initkit 1.1.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.
@@ -0,0 +1,620 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import ora from 'ora';
4
+ import chalk from 'chalk';
5
+ import { getLatestVersion } from '../utils/versionFetcher.js';
6
+
7
+ async function fetchVersion(packageName, fallback = 'latest') {
8
+ try {
9
+ const version = await getLatestVersion(packageName);
10
+ return `^${version}`;
11
+ } catch {
12
+ return fallback;
13
+ }
14
+ }
15
+
16
+ /**
17
+ * Generate Next.js project with App Router or Pages Router structure
18
+ *
19
+ * Creates a modern Next.js project with:
20
+ * - Folder structure based on user preference (feature-based, type-based, or pages-router)
21
+ * - TypeScript or JavaScript configuration
22
+ * - App Router (default) or Pages Router architecture
23
+ * - Package.json with Next.js 14+ dependencies
24
+ * - README with getting started instructions
25
+ *
26
+ * @param {string} projectPath - Absolute path to the project directory
27
+ * @param {Object} config - User configuration object
28
+ * @param {string} config.projectName - Name of the project
29
+ * @param {string} config.language - Programming language ('typescript'|'javascript')
30
+ * @param {string} [config.folderStructure='feature-based'] - Folder organization pattern
31
+ * - 'feature-based': Organize by features/modules (recommended for large apps)
32
+ * - 'type-based': Organize by file type (components, hooks, utils)
33
+ * - 'pages-router': Use legacy Pages Router instead of App Router
34
+ * @param {string} config.packageManager - Package manager to use
35
+ *
36
+ * @returns {Promise<void>}
37
+ *
38
+ * @example
39
+ * // Create Next.js project with App Router and feature-based structure
40
+ * await generateNextjsTemplate('/path/to/project', {
41
+ * projectName: 'my-nextjs-app',
42
+ * language: 'typescript',
43
+ * folderStructure: 'feature-based',
44
+ * packageManager: 'npm'
45
+ * });
46
+ */
47
+ export async function generateNextjsTemplate(projectPath, config) {
48
+ // Create folder structure only
49
+ await createNextjsFolderStructure(projectPath, config);
50
+
51
+ // Generate package.json
52
+ await generateNextjsPackageJson(projectPath, config);
53
+
54
+ // Generate essential files (layout, page, etc.)
55
+ await generateNextjsEssentialFiles(projectPath, config);
56
+
57
+ // Generate README
58
+ await generateNextjsReadme(projectPath, config);
59
+ }
60
+
61
+ async function createNextjsFolderStructure(projectPath, config) {
62
+ const srcPath = path.join(projectPath, 'src');
63
+ const folderStructure = config.folderStructure || 'feature-based';
64
+ const useAppRouter = folderStructure !== 'pages-router';
65
+
66
+ if (folderStructure === 'feature-based') {
67
+ // Feature-based structure
68
+ const features = ['auth', 'dashboard', 'users'];
69
+ for (const feature of features) {
70
+ await fs.ensureDir(path.join(srcPath, 'features', feature, 'components'));
71
+ await fs.ensureDir(path.join(srcPath, 'features', feature, 'hooks'));
72
+ await fs.ensureDir(path.join(srcPath, 'features', feature, 'services'));
73
+ await fs.ensureDir(path.join(srcPath, 'features', feature, 'types'));
74
+
75
+ // Create barrel export
76
+ await fs.writeFile(
77
+ path.join(srcPath, 'features', feature, 'index.ts'),
78
+ generateBarrelExport(feature)
79
+ );
80
+ }
81
+
82
+ // Shared directory
83
+ await fs.ensureDir(path.join(srcPath, 'shared', 'components', 'ui'));
84
+ await fs.ensureDir(path.join(srcPath, 'shared', 'components', 'layout'));
85
+ await fs.ensureDir(path.join(srcPath, 'shared', 'hooks'));
86
+ await fs.ensureDir(path.join(srcPath, 'shared', 'utils'));
87
+ await fs.ensureDir(path.join(srcPath, 'shared', 'types'));
88
+
89
+ await fs.writeFile(
90
+ path.join(srcPath, 'shared', 'components', 'index.ts'),
91
+ `// Export shared components\n// TODO: Add your shared components here\n`
92
+ );
93
+ } else if (folderStructure === 'component-based') {
94
+ // Component-based structure
95
+ await fs.ensureDir(path.join(srcPath, 'components', 'ui'));
96
+ await fs.ensureDir(path.join(srcPath, 'components', 'layout'));
97
+ await fs.ensureDir(path.join(srcPath, 'components', 'forms'));
98
+ await fs.ensureDir(path.join(srcPath, 'hooks'));
99
+ await fs.ensureDir(path.join(srcPath, 'services'));
100
+ await fs.ensureDir(path.join(srcPath, 'utils'));
101
+ await fs.ensureDir(path.join(srcPath, 'types'));
102
+ }
103
+
104
+ // App or pages directory
105
+ if (useAppRouter) {
106
+ await fs.ensureDir(path.join(srcPath, 'app'));
107
+ await fs.ensureDir(path.join(srcPath, 'app', 'api'));
108
+ } else {
109
+ await fs.ensureDir(path.join(srcPath, 'pages'));
110
+ await fs.ensureDir(path.join(srcPath, 'pages', 'api'));
111
+ }
112
+
113
+ // Common directories
114
+ await fs.ensureDir(path.join(srcPath, 'lib'));
115
+ await fs.ensureDir(path.join(projectPath, 'public'));
116
+ }
117
+
118
+ async function generateNextjsPackageJson(projectPath, config) {
119
+ const { language, styling, additionalLibraries = [] } = config;
120
+ const isTypeScript = language === 'typescript';
121
+
122
+ const spinner = ora('Fetching latest package versions...').start();
123
+
124
+ try {
125
+ // Fetch core dependencies
126
+ const [nextVer, reactVer, reactDomVer] = await Promise.all([
127
+ fetchVersion('next'),
128
+ fetchVersion('react'),
129
+ fetchVersion('react-dom'),
130
+ ]);
131
+
132
+ const dependencies = {
133
+ next: nextVer,
134
+ react: reactVer,
135
+ 'react-dom': reactDomVer,
136
+ };
137
+
138
+ const devDependencies = {};
139
+
140
+ // TypeScript dependencies
141
+ if (isTypeScript) {
142
+ const [tsVer, typesNodeVer, typesReactVer, typesReactDomVer] = await Promise.all([
143
+ fetchVersion('typescript'),
144
+ fetchVersion('@types/node'),
145
+ fetchVersion('@types/react'),
146
+ fetchVersion('@types/react-dom'),
147
+ ]);
148
+ devDependencies['typescript'] = tsVer;
149
+ devDependencies['@types/node'] = typesNodeVer;
150
+ devDependencies['@types/react'] = typesReactVer;
151
+ devDependencies['@types/react-dom'] = typesReactDomVer;
152
+ }
153
+
154
+ // Add Tailwind
155
+ if (styling === 'tailwind') {
156
+ devDependencies['tailwindcss'] = await fetchVersion('tailwindcss');
157
+ }
158
+
159
+ // Add libraries
160
+ if (additionalLibraries.includes('tanstack-query')) {
161
+ dependencies['@tanstack/react-query'] = await fetchVersion('@tanstack/react-query');
162
+ }
163
+
164
+ if (additionalLibraries.includes('redux-toolkit')) {
165
+ const [reduxVer, reactReduxVer] = await Promise.all([
166
+ fetchVersion('@reduxjs/toolkit'),
167
+ fetchVersion('react-redux'),
168
+ ]);
169
+ dependencies['@reduxjs/toolkit'] = reduxVer;
170
+ dependencies['react-redux'] = reactReduxVer;
171
+ }
172
+
173
+ if (additionalLibraries.includes('zustand')) {
174
+ dependencies['zustand'] = await fetchVersion('zustand');
175
+ }
176
+
177
+ if (additionalLibraries.includes('jotai')) {
178
+ dependencies['jotai'] = await fetchVersion('jotai');
179
+ }
180
+
181
+ if (additionalLibraries.includes('axios')) {
182
+ dependencies['axios'] = await fetchVersion('axios');
183
+ }
184
+
185
+ if (additionalLibraries.includes('zod')) {
186
+ dependencies['zod'] = await fetchVersion('zod');
187
+ }
188
+
189
+ if (additionalLibraries.includes('react-hook-form')) {
190
+ const [formVer, resolversVer] = await Promise.all([
191
+ fetchVersion('react-hook-form'),
192
+ fetchVersion('@hookform/resolvers'),
193
+ ]);
194
+ dependencies['react-hook-form'] = formVer;
195
+ dependencies['@hookform/resolvers'] = resolversVer;
196
+ }
197
+
198
+ if (additionalLibraries.includes('framer-motion')) {
199
+ dependencies['framer-motion'] = await fetchVersion('framer-motion');
200
+ }
201
+
202
+ if (additionalLibraries.includes('react-icons')) {
203
+ dependencies['react-icons'] = await fetchVersion('react-icons');
204
+ }
205
+
206
+ if (additionalLibraries.includes('radix-ui')) {
207
+ const [dialogVer, dropdownVer, selectVer, slotVer] = await Promise.all([
208
+ fetchVersion('@radix-ui/react-dialog'),
209
+ fetchVersion('@radix-ui/react-dropdown-menu'),
210
+ fetchVersion('@radix-ui/react-select'),
211
+ fetchVersion('@radix-ui/react-slot'),
212
+ ]);
213
+ Object.assign(dependencies, {
214
+ '@radix-ui/react-dialog': dialogVer,
215
+ '@radix-ui/react-dropdown-menu': dropdownVer,
216
+ '@radix-ui/react-select': selectVer,
217
+ '@radix-ui/react-slot': slotVer,
218
+ });
219
+ }
220
+
221
+ spinner.succeed(chalk.green('Fetched latest versions'));
222
+
223
+ const packageJson = {
224
+ name: config.projectName,
225
+ version: '0.1.0',
226
+ private: true,
227
+ scripts: {
228
+ dev: 'next dev',
229
+ build: 'next build',
230
+ start: 'next start',
231
+ lint: 'next lint',
232
+ },
233
+ dependencies,
234
+ devDependencies,
235
+ };
236
+
237
+ await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
238
+ } catch (error) {
239
+ spinner.fail(chalk.yellow('Could not fetch versions, using fallbacks'));
240
+
241
+ // Fallback with latest tag
242
+ const dependencies = { next: 'latest', react: 'latest', 'react-dom': 'latest' };
243
+ const devDependencies = isTypeScript
244
+ ? {
245
+ typescript: 'latest',
246
+ '@types/node': 'latest',
247
+ '@types/react': 'latest',
248
+ '@types/react-dom': 'latest',
249
+ }
250
+ : {};
251
+
252
+ if (styling === 'tailwind') devDependencies['tailwindcss'] = 'latest';
253
+ if (additionalLibraries.includes('tanstack-query'))
254
+ dependencies['@tanstack/react-query'] = 'latest';
255
+ if (additionalLibraries.includes('redux-toolkit')) {
256
+ dependencies['@reduxjs/toolkit'] = 'latest';
257
+ dependencies['react-redux'] = 'latest';
258
+ }
259
+ if (additionalLibraries.includes('zustand')) dependencies['zustand'] = 'latest';
260
+ if (additionalLibraries.includes('jotai')) dependencies['jotai'] = 'latest';
261
+ if (additionalLibraries.includes('axios')) dependencies['axios'] = 'latest';
262
+ if (additionalLibraries.includes('zod')) dependencies['zod'] = 'latest';
263
+ if (additionalLibraries.includes('react-hook-form')) {
264
+ dependencies['react-hook-form'] = 'latest';
265
+ dependencies['@hookform/resolvers'] = 'latest';
266
+ }
267
+ if (additionalLibraries.includes('framer-motion')) dependencies['framer-motion'] = 'latest';
268
+ if (additionalLibraries.includes('react-icons')) dependencies['react-icons'] = 'latest';
269
+ if (additionalLibraries.includes('radix-ui')) {
270
+ Object.assign(dependencies, {
271
+ '@radix-ui/react-dialog': 'latest',
272
+ '@radix-ui/react-dropdown-menu': 'latest',
273
+ '@radix-ui/react-select': 'latest',
274
+ '@radix-ui/react-slot': 'latest',
275
+ });
276
+ }
277
+
278
+ const packageJson = {
279
+ name: config.projectName,
280
+ version: '0.1.0',
281
+ private: true,
282
+ scripts: {
283
+ dev: 'next dev',
284
+ build: 'next build',
285
+ start: 'next start',
286
+ lint: 'next lint',
287
+ },
288
+ dependencies,
289
+ devDependencies,
290
+ };
291
+
292
+ await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
293
+ }
294
+ }
295
+
296
+ async function generateNextjsEssentialFiles(projectPath, config) {
297
+ const { language, folderStructure } = config;
298
+ const isTypeScript = language === 'typescript';
299
+ const ext = isTypeScript ? 'tsx' : 'jsx';
300
+ const useAppRouter = folderStructure !== 'pages-router';
301
+ const appPath = path.join(projectPath, 'src', useAppRouter ? 'app' : 'pages');
302
+
303
+ if (useAppRouter) {
304
+ // Create layout file for App Router
305
+ const layoutContent = `${isTypeScript ? "import type { Metadata } from 'next'\n" : ''}import './globals.css'
306
+
307
+ ${
308
+ isTypeScript
309
+ ? `export const metadata: Metadata = {
310
+ title: '${config.projectName}',
311
+ description: 'Generated by InitKit CLI',
312
+ }
313
+
314
+ `
315
+ : ''
316
+ }export default function RootLayout(${
317
+ isTypeScript
318
+ ? `{
319
+ children,
320
+ }: Readonly<{
321
+ children: React.ReactNode
322
+ }>`
323
+ : '{ children }'
324
+ }) {
325
+ return (
326
+ <html lang="en">
327
+ <body>{children}</body>
328
+ </html>
329
+ )
330
+ }
331
+ `;
332
+
333
+ await fs.writeFile(path.join(appPath, `layout.${ext}`), layoutContent);
334
+
335
+ // Create page file for App Router
336
+ const pageContent = `export default function Home() {
337
+ return (
338
+ <div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
339
+ <h1>Welcome to ${config.projectName}</h1>
340
+ <p>Get started by editing <code>src/app/page.${ext}</code></p>
341
+ </div>
342
+ )
343
+ }
344
+ `;
345
+
346
+ await fs.writeFile(path.join(appPath, `page.${ext}`), pageContent);
347
+
348
+ // Create globals.css
349
+ const globalsCssContent = `:root {
350
+ --foreground-rgb: 0, 0, 0;
351
+ --background-start-rgb: 214, 219, 220;
352
+ --background-end-rgb: 255, 255, 255;
353
+ }
354
+
355
+ @media (prefers-color-scheme: dark) {
356
+ :root {
357
+ --foreground-rgb: 255, 255, 255;
358
+ --background-start-rgb: 0, 0, 0;
359
+ --background-end-rgb: 0, 0, 0;
360
+ }
361
+ }
362
+
363
+ body {
364
+ color: rgb(var(--foreground-rgb));
365
+ background: linear-gradient(
366
+ to bottom,
367
+ transparent,
368
+ rgb(var(--background-end-rgb))
369
+ )
370
+ rgb(var(--background-start-rgb));
371
+ }
372
+
373
+ * {
374
+ box-sizing: border-box;
375
+ padding: 0;
376
+ margin: 0;
377
+ }
378
+
379
+ html,
380
+ body {
381
+ max-width: 100vw;
382
+ overflow-x: hidden;
383
+ }
384
+
385
+ a {
386
+ color: inherit;
387
+ text-decoration: none;
388
+ }
389
+ `;
390
+
391
+ await fs.writeFile(path.join(appPath, 'globals.css'), globalsCssContent);
392
+ } else {
393
+ // Create _app file for Pages Router
394
+ const appPageContent = `${isTypeScript ? "import type { AppProps } from 'next/app'\n" : ''}import '../styles/globals.css'
395
+
396
+ export default function App(${isTypeScript ? '{ Component, pageProps }: AppProps' : '{ Component, pageProps }'}) {
397
+ return <Component {...pageProps} />
398
+ }
399
+ `;
400
+
401
+ await fs.writeFile(path.join(appPath, `_app.${ext}`), appPageContent);
402
+
403
+ // Create index page for Pages Router
404
+ const indexPageContent = `export default function Home() {
405
+ return (
406
+ <div style={{ padding: '2rem', fontFamily: 'system-ui, sans-serif' }}>
407
+ <h1>Welcome to ${config.projectName}</h1>
408
+ <p>Get started by editing <code>src/pages/index.${ext}</code></p>
409
+ </div>
410
+ )
411
+ }
412
+ `;
413
+
414
+ await fs.writeFile(path.join(appPath, `index.${ext}`), indexPageContent);
415
+
416
+ // Create styles directory
417
+ const stylesPath = path.join(projectPath, 'src', 'styles');
418
+ await fs.ensureDir(stylesPath);
419
+ await fs.writeFile(
420
+ path.join(stylesPath, 'globals.css'),
421
+ `:root {
422
+ --foreground-rgb: 0, 0, 0;
423
+ --background-start-rgb: 214, 219, 220;
424
+ --background-end-rgb: 255, 255, 255;
425
+ }
426
+
427
+ body {
428
+ color: rgb(var(--foreground-rgb));
429
+ background: linear-gradient(
430
+ to bottom,
431
+ transparent,
432
+ rgb(var(--background-end-rgb))
433
+ )
434
+ rgb(var(--background-start-rgb));
435
+ }
436
+
437
+ * {
438
+ box-sizing: border-box;
439
+ padding: 0;
440
+ margin: 0;
441
+ }
442
+ `
443
+ );
444
+ }
445
+
446
+ // Create next.config file
447
+ const nextConfigContent = isTypeScript
448
+ ? `import type { NextConfig } from 'next'
449
+
450
+ const nextConfig: NextConfig = {
451
+ /* config options here */
452
+ }
453
+
454
+ export default nextConfig
455
+ `
456
+ : `/** @type {import('next').NextConfig} */
457
+ const nextConfig = {
458
+ /* config options here */
459
+ }
460
+
461
+ export default nextConfig
462
+ `;
463
+
464
+ await fs.writeFile(
465
+ path.join(projectPath, isTypeScript ? 'next.config.ts' : 'next.config.js'),
466
+ nextConfigContent
467
+ );
468
+
469
+ // Create .gitignore
470
+ const gitignoreContent = `# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
471
+
472
+ # dependencies
473
+ /node_modules
474
+ /.pnp
475
+ .pnp.js
476
+ .yarn/install-state.gz
477
+
478
+ # testing
479
+ /coverage
480
+
481
+ # next.js
482
+ /.next/
483
+ /out/
484
+
485
+ # production
486
+ /build
487
+
488
+ # misc
489
+ .DS_Store
490
+ *.pem
491
+
492
+ # debug
493
+ npm-debug.log*
494
+ yarn-debug.log*
495
+ yarn-error.log*
496
+
497
+ # local env files
498
+ .env*.local
499
+
500
+ # vercel
501
+ .vercel
502
+
503
+ # typescript
504
+ *.tsbuildinfo
505
+ next-env.d.ts
506
+ `;
507
+
508
+ await fs.writeFile(path.join(projectPath, '.gitignore'), gitignoreContent);
509
+
510
+ // Create tsconfig.json if TypeScript
511
+ if (isTypeScript) {
512
+ const tsconfigContent = {
513
+ compilerOptions: {
514
+ target: 'ES2017',
515
+ lib: ['dom', 'dom.iterable', 'esnext'],
516
+ allowJs: true,
517
+ skipLibCheck: true,
518
+ strict: true,
519
+ noEmit: true,
520
+ esModuleInterop: true,
521
+ module: 'esnext',
522
+ moduleResolution: 'bundler',
523
+ resolveJsonModule: true,
524
+ isolatedModules: true,
525
+ jsx: 'preserve',
526
+ incremental: true,
527
+ plugins: [
528
+ {
529
+ name: 'next',
530
+ },
531
+ ],
532
+ paths: {
533
+ '@/*': ['./src/*'],
534
+ },
535
+ },
536
+ include: ['next-env.d.ts', 'src/**/*.ts', 'src/**/*.tsx', '.next/types/**/*.ts'],
537
+ exclude: ['node_modules'],
538
+ };
539
+
540
+ await fs.writeJSON(path.join(projectPath, 'tsconfig.json'), tsconfigContent, { spaces: 2 });
541
+ }
542
+ }
543
+
544
+ async function generateNextjsReadme(projectPath, config) {
545
+ const { projectName, folderStructure, language, styling, packageManager } = config;
546
+
547
+ const readme = `# ${projectName}
548
+
549
+ Created with InitKit CLI
550
+
551
+ ## Setup
552
+
553
+ 1. Install dependencies:
554
+ \`\`\`bash
555
+ ${packageManager} install
556
+ \`\`\`
557
+
558
+ 2. Run the development server:
559
+ \`\`\`bash
560
+ ${packageManager} ${packageManager === 'npm' ? 'run ' : ''}dev
561
+ \`\`\`
562
+
563
+ 3. Open [http://localhost:3000](http://localhost:3000)
564
+
565
+ ## Tech Stack
566
+
567
+ - **Next.js 15** - React framework
568
+ - **React 19** - UI library${language === 'typescript' ? '\n- **TypeScript** - Type safety' : ''}${styling === 'tailwind' ? '\n- **Tailwind CSS v4** - Styling' : ''}
569
+
570
+ ## Folder Structure
571
+
572
+ \`\`\`
573
+ src/
574
+ ${
575
+ folderStructure === 'feature-based'
576
+ ? `├── features/ # Feature modules
577
+ │ ├── auth/ # Authentication
578
+ │ ├── dashboard/ # Dashboard
579
+ │ └── users/ # Users
580
+ ├── shared/ # Shared code
581
+ ├── app/ # Next.js App Router`
582
+ : `├── components/ # React components
583
+ ├── hooks/ # Custom hooks
584
+ ├── services/ # API services
585
+ ├── app/ # Next.js App Router`
586
+ }
587
+ ├── lib/ # Utilities
588
+ └── public/ # Static files
589
+ \`\`\`
590
+
591
+ ## Next Steps
592
+
593
+ 1. Run \`npx create-next-app@latest . --use-${packageManager}\` to initialize Next.js${styling === 'tailwind' ? '\n2. Install Tailwind v4: `' + packageManager + (packageManager === 'npm' ? ' install' : ' add') + ' tailwindcss@next`' : ''}
594
+ 3. Start building in \`src/features/\`
595
+ 4. Add environment variables in \`.env.local\`
596
+
597
+ ---
598
+
599
+ Built with InitKit
600
+ `;
601
+
602
+ await fs.writeFile(path.join(projectPath, 'README.md'), readme);
603
+ }
604
+
605
+ function generateBarrelExport(featureName) {
606
+ return `// ${featureName.toUpperCase()} Feature
607
+
608
+ // TODO: Export your components
609
+ // export { default as ${featureName.charAt(0).toUpperCase() + featureName.slice(1)}Component } from './components/${featureName.charAt(0).toUpperCase() + featureName.slice(1)}Component';
610
+
611
+ // TODO: Export your hooks
612
+ // export { use${featureName.charAt(0).toUpperCase() + featureName.slice(1)} } from './hooks/use${featureName.charAt(0).toUpperCase() + featureName.slice(1)}';
613
+
614
+ // TODO: Export your services
615
+ // export * from './services/${featureName}Service';
616
+
617
+ // TODO: Export your types
618
+ // export type * from './types/${featureName}.types';
619
+ `;
620
+ }