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,365 @@
1
+ import fs from 'fs-extra';
2
+ import path from 'path';
3
+ import { generateNextjsTemplate } from '../templates/nextjs.js';
4
+ import { generateReactTemplate } from '../templates/react.js';
5
+ import { generateVueTemplate } from '../templates/vue.js';
6
+ import { generateExpressTemplate } from '../templates/express.js';
7
+ import { generateFullStackTemplate } from '../templates/fullstack.js';
8
+
9
+ /**
10
+ * Generate project files from templates based on user configuration
11
+ * @param {string} projectPath - Absolute path to the project directory
12
+ * @param {Object} config - User's project configuration object
13
+ * @param {string} config.projectType - Type of project ('frontend'|'backend'|'fullstack'|'library')
14
+ * @param {string} [config.frontend] - Frontend framework choice
15
+ * @param {string} [config.backend] - Backend framework choice
16
+ * @param {Array<string>} [config.features] - Additional features to enable
17
+ * @returns {Promise<void>}
18
+ * @throws {Error} If template generation fails
19
+ */
20
+ async function generateTemplate(projectPath, config) {
21
+ // Generate .gitignore
22
+ await generateGitignore(projectPath, config);
23
+
24
+ // Generate project-specific files based on type
25
+ switch (config.projectType) {
26
+ case 'frontend':
27
+ await generateFrontendFiles(projectPath, config);
28
+ break;
29
+ case 'backend':
30
+ await generateBackendFiles(projectPath, config);
31
+ break;
32
+ case 'fullstack':
33
+ await generateFullStackFiles(projectPath, config);
34
+ break;
35
+ case 'library':
36
+ await generateLibraryFiles(projectPath, config);
37
+ break;
38
+ }
39
+
40
+ // Add additional features (Docker, GitHub Actions, etc.)
41
+ if (config.features) {
42
+ await addFeatures(projectPath, config);
43
+ }
44
+ }
45
+
46
+ /**
47
+ * Generate frontend project files based on selected framework
48
+ * @param {string} projectPath - Absolute path to the project directory
49
+ * @param {Object} config - User's project configuration
50
+ * @param {string} config.frontend - Frontend framework ('react'|'vue'|'nextjs'|'angular'|'svelte'|'nuxt')
51
+ * @param {string} config.language - Language choice ('typescript'|'javascript')
52
+ * @param {string} config.folderStructure - Folder structure preference
53
+ * @returns {Promise<void>}
54
+ * @throws {Error} If framework template is not found or generation fails
55
+ */
56
+ async function generateFrontendFiles(projectPath, config) {
57
+ const framework = config.frontend;
58
+
59
+ switch (framework) {
60
+ case 'nextjs':
61
+ await generateNextjsTemplate(projectPath, config);
62
+ break;
63
+ case 'react':
64
+ await generateReactTemplate(projectPath, config);
65
+ break;
66
+ case 'vue':
67
+ await generateVueTemplate(projectPath, config);
68
+ break;
69
+ default:
70
+ throw new Error(
71
+ `Frontend framework "${framework}" is not yet implemented. Available: react, nextjs, vue`
72
+ );
73
+ }
74
+ }
75
+
76
+ /**
77
+ * Generate backend project files based on selected framework
78
+ * @param {string} projectPath - Absolute path to the project directory
79
+ * @param {Object} config - User's project configuration
80
+ * @param {string} config.backend - Backend framework ('express'|'fastify'|'nestjs'|'koa'|'hapi')
81
+ * @param {string} [config.database] - Database choice ('mongodb'|'postgresql'|'mysql'|'sqlite')
82
+ * @param {string} config.language - Language choice ('typescript'|'javascript')
83
+ * @param {string} config.projectName - Name of the project
84
+ * @param {string} config.packageManager - Package manager to use
85
+ * @returns {Promise<void>}
86
+ * @throws {Error} If backend template generation fails
87
+ */
88
+ async function generateBackendFiles(projectPath, config) {
89
+ const backend = config.backend;
90
+
91
+ switch (backend) {
92
+ case 'express':
93
+ await generateExpressTemplate(projectPath, config);
94
+ break;
95
+ default:
96
+ throw new Error(`Backend framework "${backend}" is not yet implemented. Available: express`);
97
+ }
98
+ }
99
+
100
+ /**
101
+ * Generate full-stack project with both frontend and backend
102
+ * @param {string} projectPath - Absolute path to the project directory
103
+ * @param {Object} config - User's project configuration
104
+ * @param {string} [config.fullstackType] - Project structure type ('monorepo'|'traditional')
105
+ * @param {string} [config.stack] - Technology stack ('MERN'|'PERN'|'T3'|etc)
106
+ * @param {string} [config.frontend] - Frontend framework
107
+ * @param {string} [config.backend] - Backend framework
108
+ * @returns {Promise<void>}
109
+ * @throws {Error} If full-stack template generation fails
110
+ */
111
+ async function generateFullStackFiles(projectPath, config) {
112
+ // Use the dedicated full-stack template generator
113
+ await generateFullStackTemplate(projectPath, config);
114
+ }
115
+
116
+ /**
117
+ * Generate Node.js library/package structure
118
+ * @param {string} projectPath - Absolute path to the project directory
119
+ * @param {Object} config - User's project configuration
120
+ * @param {string} config.projectName - Name of the library
121
+ * @param {string} config.language - Language choice ('typescript'|'javascript')
122
+ * @param {string} config.packageManager - Package manager to use
123
+ * @returns {Promise<void>}
124
+ * @throws {Error} If library template generation fails
125
+ */
126
+ async function generateLibraryFiles(projectPath, config) {
127
+ const srcPath = path.join(projectPath, 'src');
128
+ await fs.ensureDir(srcPath);
129
+
130
+ const ext = config.language === 'typescript' ? 'ts' : 'js';
131
+
132
+ const indexContent = `/**
133
+ * Main entry point for ${config.projectName}
134
+ */
135
+
136
+ export function hello() {
137
+ return 'Hello from ${config.projectName}!';
138
+ }
139
+ `;
140
+
141
+ await fs.writeFile(path.join(srcPath, `index.${ext}`), indexContent);
142
+
143
+ // Package.json for library
144
+ const packageJson = {
145
+ name: config.projectName,
146
+ version: '1.0.0',
147
+ description: 'A reusable library',
148
+ main: `dist/index.js`,
149
+ types: config.language === 'typescript' ? 'dist/index.d.ts' : undefined,
150
+ files: ['dist'],
151
+ scripts: {
152
+ build: 'echo "Configure build script"',
153
+ test: 'echo "Configure test script"',
154
+ },
155
+ keywords: [],
156
+ author: '',
157
+ license: 'MIT',
158
+ };
159
+
160
+ await fs.writeJSON(path.join(projectPath, 'package.json'), packageJson, { spaces: 2 });
161
+
162
+ const readme = `# ${config.projectName}
163
+
164
+ Library created with InitKit CLI
165
+
166
+ ## Installation
167
+
168
+ \`\`\`bash
169
+ npm install ${config.projectName}
170
+ \`\`\`
171
+
172
+ ## Usage
173
+
174
+ \`\`\`javascript
175
+ import { hello } from '${config.projectName}';
176
+
177
+ console.log(hello());
178
+ \`\`\`
179
+
180
+ ---
181
+
182
+ Built with InitKit
183
+ `;
184
+
185
+ await fs.writeFile(path.join(projectPath, 'README.md'), readme);
186
+ }
187
+
188
+ /**
189
+ * Generate .gitignore file with framework-specific patterns
190
+ * @param {string} projectPath - Absolute path to the project directory
191
+ * @param {Object} config - User's project configuration
192
+ * @param {string} config.projectType - Type of project ('frontend'|'backend'|'fullstack'|'library')
193
+ * @param {string} [config.frontend] - Frontend framework name
194
+ * @param {string} [config.backend] - Backend framework name
195
+ * @param {boolean} [config.monorepo] - Whether project uses monorepo structure
196
+ * @returns {Promise<void>}
197
+ * @throws {Error} If file creation fails
198
+ */
199
+ async function generateGitignore(projectPath, _config) {
200
+ const gitignore = `# Dependencies
201
+ node_modules/
202
+ .pnp
203
+ .pnp.js
204
+
205
+ # Testing
206
+ coverage/
207
+
208
+ # Production
209
+ build/
210
+ dist/
211
+ .next/
212
+ out/
213
+
214
+ # Misc
215
+ .DS_Store
216
+ *.pem
217
+
218
+ # Debug
219
+ npm-debug.log*
220
+ yarn-debug.log*
221
+ yarn-error.log*
222
+ .pnpm-debug.log*
223
+
224
+ # Local env files
225
+ .env
226
+ .env*.local
227
+
228
+ # Vercel
229
+ .vercel
230
+
231
+ # TypeScript
232
+ *.tsbuildinfo
233
+ next-env.d.ts
234
+
235
+ # IDE
236
+ .vscode/
237
+ .idea/
238
+ *.swp
239
+ *.swo
240
+ *~
241
+ `;
242
+
243
+ await fs.writeFile(path.join(projectPath, '.gitignore'), gitignore);
244
+ }
245
+
246
+ /**
247
+ * Add additional features to the project (Docker, CI/CD, Git hooks)
248
+ * @param {string} projectPath - Absolute path to the project directory
249
+ * @param {Object} config - User's project configuration
250
+ * @param {Array<string>} config.features - List of features to add (e.g., ['docker', 'github-actions', 'husky'])
251
+ * @returns {Promise<void>}
252
+ */
253
+ async function addFeatures(projectPath, config) {
254
+ const features = config.features || [];
255
+
256
+ // Docker configuration
257
+ if (features.includes('docker')) {
258
+ await generateDockerFiles(projectPath, config);
259
+ }
260
+
261
+ // GitHub Actions
262
+ if (features.includes('github-actions')) {
263
+ await generateGitHubActions(projectPath, config);
264
+ }
265
+
266
+ // Husky
267
+ if (features.includes('husky')) {
268
+ await generateHuskyFiles(projectPath, config);
269
+ }
270
+ }
271
+
272
+ /**
273
+ * Generate Docker configuration files (Dockerfile and .dockerignore)
274
+ * @param {string} projectPath - Absolute path to the project directory
275
+ * @param {Object} config - User's project configuration
276
+ * @returns {Promise<void>}
277
+ */
278
+ async function generateDockerFiles(projectPath, _config) {
279
+ const dockerfile = `FROM node:18-alpine
280
+
281
+ WORKDIR /app
282
+
283
+ COPY package*.json ./
284
+
285
+ RUN npm ci --only=production
286
+
287
+ COPY . .
288
+
289
+ EXPOSE 3000
290
+
291
+ CMD ["npm", "start"]
292
+ `;
293
+
294
+ await fs.writeFile(path.join(projectPath, 'Dockerfile'), dockerfile);
295
+
296
+ const dockerignore = `node_modules
297
+ .git
298
+ .env
299
+ npm-debug.log
300
+ `;
301
+
302
+ await fs.writeFile(path.join(projectPath, '.dockerignore'), dockerignore);
303
+ }
304
+
305
+ /**
306
+ * Generate GitHub Actions CI/CD workflow
307
+ * @param {string} projectPath - Absolute path to the project directory
308
+ * @param {Object} config - User's project configuration
309
+ * @returns {Promise<void>}
310
+ */
311
+ async function generateGitHubActions(projectPath, _config) {
312
+ await fs.ensureDir(path.join(projectPath, '.github', 'workflows'));
313
+
314
+ const workflow = `name: CI
315
+
316
+ on:
317
+ push:
318
+ branches: [ main, develop ]
319
+ pull_request:
320
+ branches: [ main ]
321
+
322
+ jobs:
323
+ build:
324
+ runs-on: ubuntu-latest
325
+
326
+ steps:
327
+ - uses: actions/checkout@v3
328
+
329
+ - name: Use Node.js
330
+ uses: actions/setup-node@v3
331
+ with:
332
+ node-version: '18'
333
+
334
+ - name: Install dependencies
335
+ run: npm ci
336
+
337
+ - name: Run lint
338
+ run: npm run lint
339
+
340
+ - name: Run tests
341
+ run: npm test
342
+ `;
343
+
344
+ await fs.writeFile(path.join(projectPath, '.github', 'workflows', 'ci.yml'), workflow);
345
+ }
346
+
347
+ /**
348
+ * Generate Husky Git hooks for pre-commit linting
349
+ * @param {string} projectPath - Absolute path to the project directory
350
+ * @param {Object} config - User's project configuration
351
+ * @returns {Promise<void>}
352
+ */
353
+ async function generateHuskyFiles(projectPath, _config) {
354
+ await fs.ensureDir(path.join(projectPath, '.husky'));
355
+
356
+ const preCommit = `#!/usr/bin/env sh
357
+ . "$(dirname -- "$0")/_/husky.sh"
358
+
359
+ npm run lint
360
+ `;
361
+
362
+ await fs.writeFile(path.join(projectPath, '.husky', 'pre-commit'), preCommit);
363
+ }
364
+
365
+ export { generateTemplate };
@@ -0,0 +1,186 @@
1
+ import chalk from 'chalk';
2
+ import validateNpmName from 'validate-npm-package-name';
3
+ import fs from 'fs-extra';
4
+ import path from 'path';
5
+
6
+ /**
7
+ * Validate project name against npm package naming rules
8
+ * @param {string} name - Project name to validate
9
+ * @returns {Object} - { valid: boolean, errors: string[] }
10
+ */
11
+ export function validateProjectName(name) {
12
+ const errors = [];
13
+
14
+ // Check if name is provided
15
+ if (!name || name.trim() === '') {
16
+ errors.push('Project name is required');
17
+ return { valid: false, errors };
18
+ }
19
+
20
+ // Trim and convert to lowercase for validation
21
+ const trimmedName = name.trim();
22
+
23
+ // Check for spaces
24
+ if (trimmedName.includes(' ')) {
25
+ errors.push('Project name cannot contain spaces. Use hyphens instead.');
26
+ }
27
+
28
+ // Check length
29
+ if (trimmedName.length > 214) {
30
+ errors.push('Project name must be 214 characters or less');
31
+ }
32
+
33
+ if (trimmedName.length === 0) {
34
+ errors.push('Project name cannot be empty');
35
+ }
36
+
37
+ // Check if starts with . or _
38
+ if (trimmedName.startsWith('.') || trimmedName.startsWith('_')) {
39
+ errors.push('Project name cannot start with a dot or underscore');
40
+ }
41
+
42
+ // Check for uppercase letters
43
+ if (trimmedName !== trimmedName.toLowerCase()) {
44
+ errors.push('Project name must be lowercase');
45
+ }
46
+
47
+ // Check for invalid characters
48
+ const invalidChars = /[~'!()*]/;
49
+ if (invalidChars.test(trimmedName)) {
50
+ errors.push('Project name contains invalid characters: ~\'!()*');
51
+ }
52
+
53
+ // Use npm's official validator
54
+ const npmValidation = validateNpmName(trimmedName);
55
+
56
+ if (!npmValidation.validForNewPackages) {
57
+ if (npmValidation.errors) {
58
+ errors.push(...npmValidation.errors);
59
+ }
60
+ if (npmValidation.warnings) {
61
+ errors.push(...npmValidation.warnings);
62
+ }
63
+ }
64
+
65
+ return {
66
+ valid: errors.length === 0,
67
+ errors: [...new Set(errors)], // Remove duplicates
68
+ };
69
+ }
70
+
71
+ /**
72
+ * Check if directory already exists
73
+ * @param {string} projectName - Name of the project
74
+ * @param {string} basePath - Base path where project will be created (default: cwd)
75
+ * @returns {Object} - { exists: boolean, path: string }
76
+ */
77
+ export function checkDirectoryExists(projectName, basePath = process.cwd()) {
78
+ const projectPath = path.join(basePath, projectName);
79
+ const exists = fs.existsSync(projectPath);
80
+
81
+ return {
82
+ exists,
83
+ path: projectPath,
84
+ };
85
+ }
86
+
87
+ /**
88
+ * Validate that the target directory is writable
89
+ * @param {string} dirPath - Directory path to check
90
+ * @returns {Object} - { writable: boolean, error: string }
91
+ */
92
+ export async function checkDirectoryWritable(dirPath) {
93
+ try {
94
+ await fs.access(dirPath, fs.constants.W_OK);
95
+ return { writable: true, error: null };
96
+ } catch (error) {
97
+ return {
98
+ writable: false,
99
+ error: `Directory ${dirPath} is not writable`,
100
+ };
101
+ }
102
+ }
103
+
104
+ /**
105
+ * Validate folder structure name
106
+ * @param {string} structure - Folder structure type
107
+ * @returns {boolean}
108
+ */
109
+ export function validateFolderStructure(structure) {
110
+ const validStructures = ['feature-based', 'type-based', 'domain-driven', 'flat'];
111
+ return validStructures.includes(structure);
112
+ }
113
+
114
+ /**
115
+ * Format validation errors for display
116
+ * @param {string[]} errors - Array of error messages
117
+ * @returns {string} - Formatted error message
118
+ */
119
+ export function formatValidationErrors(errors) {
120
+ if (errors.length === 0) return '';
121
+
122
+ if (errors.length === 1) {
123
+ return chalk.red(`Error: ${errors[0]}`);
124
+ }
125
+
126
+ const errorList = errors.map((err, idx) => ` ${idx + 1}. ${err}`).join('\n');
127
+ return chalk.red(`Invalid project name:\n${errorList}`);
128
+ }
129
+
130
+ /**
131
+ * Sanitize project name by fixing common issues
132
+ * @param {string} name - Project name to sanitize
133
+ * @returns {string} - Sanitized name
134
+ */
135
+ export function sanitizeProjectName(name) {
136
+ return name
137
+ .trim()
138
+ .toLowerCase()
139
+ .replace(/\s+/g, '-') // Replace spaces with hyphens
140
+ .replace(/[^a-z0-9-_]/g, '') // Remove invalid characters
141
+ .replace(/^[._]/, '') // Remove leading dots/underscores
142
+ .substring(0, 214); // Limit length
143
+ }
144
+
145
+ /**
146
+ * Suggest an alternative project name if the current one is invalid
147
+ * @param {string} name - Original project name
148
+ * @returns {string} - Suggested name
149
+ */
150
+ export function suggestProjectName(name) {
151
+ const sanitized = sanitizeProjectName(name);
152
+ if (sanitized.length === 0) {
153
+ return 'my-awesome-project';
154
+ }
155
+ return sanitized;
156
+ }
157
+
158
+ /**
159
+ * Validate multiple inputs at once
160
+ * @param {Object} inputs - Object with input values to validate
161
+ * @returns {Object} - { valid: boolean, errors: Object }
162
+ */
163
+ export function validateAllInputs(inputs) {
164
+ const errors = {};
165
+ let valid = true;
166
+
167
+ // Validate project name
168
+ if (inputs.projectName) {
169
+ const nameValidation = validateProjectName(inputs.projectName);
170
+ if (!nameValidation.valid) {
171
+ errors.projectName = nameValidation.errors;
172
+ valid = false;
173
+ }
174
+ }
175
+
176
+ // Check directory existence
177
+ if (inputs.projectName && inputs.checkDirectory !== false) {
178
+ const dirCheck = checkDirectoryExists(inputs.projectName);
179
+ if (dirCheck.exists) {
180
+ errors.directory = [`Directory "${inputs.projectName}" already exists`];
181
+ valid = false;
182
+ }
183
+ }
184
+
185
+ return { valid, errors };
186
+ }
@@ -0,0 +1,128 @@
1
+ import https from 'https';
2
+
3
+ /**
4
+ * Fetch the latest version of a package from npm registry
5
+ * @param {string} packageName - Name of the npm package
6
+ * @returns {Promise<string>} Latest version number
7
+ */
8
+ export async function getLatestVersion(packageName) {
9
+ return new Promise((resolve, reject) => {
10
+ const url = `https://registry.npmjs.org/${packageName}/latest`;
11
+
12
+ https
13
+ .get(url, (res) => {
14
+ let data = '';
15
+
16
+ res.on('data', (chunk) => {
17
+ data += chunk;
18
+ });
19
+
20
+ res.on('end', () => {
21
+ try {
22
+ const json = JSON.parse(data);
23
+ resolve(json.version || 'latest');
24
+ } catch (error) {
25
+ // If parsing fails, return 'latest' as fallback
26
+ resolve('latest');
27
+ }
28
+ });
29
+ })
30
+ .on('error', (error) => {
31
+ // On network error, return 'latest' as fallback
32
+ console.warn(`Warning: Could not fetch version for ${packageName}, using 'latest'`);
33
+ resolve('latest');
34
+ });
35
+ });
36
+ }
37
+
38
+ /**
39
+ * Fetch latest versions for multiple packages
40
+ * @param {Object} packages - Object with package names as keys
41
+ * @returns {Promise<Object>} Object with package names as keys and latest versions as values
42
+ */
43
+ export async function getLatestVersions(packages) {
44
+ const packageNames = Object.keys(packages);
45
+ const versions = {};
46
+
47
+ await Promise.all(
48
+ packageNames.map(async (name) => {
49
+ try {
50
+ const version = await getLatestVersion(name);
51
+ versions[name] = `^${version}`;
52
+ } catch (error) {
53
+ versions[name] = 'latest';
54
+ }
55
+ })
56
+ );
57
+
58
+ return versions;
59
+ }
60
+
61
+ /**
62
+ * Get latest versions for common dependencies
63
+ * @returns {Promise<Object>} Object with latest versions
64
+ */
65
+ export async function getLatestDependencies() {
66
+ const commonPackages = {
67
+ // React ecosystem
68
+ react: null,
69
+ 'react-dom': null,
70
+ '@types/react': null,
71
+ '@types/react-dom': null,
72
+ '@vitejs/plugin-react': null,
73
+ vite: null,
74
+ typescript: null,
75
+
76
+ // Next.js
77
+ next: null,
78
+ '@types/node': null,
79
+
80
+ // Vue
81
+ vue: null,
82
+ '@vitejs/plugin-vue': null,
83
+ 'vue-tsc': null,
84
+ 'vue-router': null,
85
+ pinia: null,
86
+
87
+ // State Management
88
+ '@reduxjs/toolkit': null,
89
+ 'react-redux': null,
90
+ zustand: null,
91
+ jotai: null,
92
+
93
+ // Routing
94
+ 'react-router-dom': null,
95
+
96
+ // Data Fetching
97
+ '@tanstack/react-query': null,
98
+ '@tanstack/vue-query': null,
99
+
100
+ // Forms
101
+ 'react-hook-form': null,
102
+ '@hookform/resolvers': null,
103
+
104
+ // Validation
105
+ zod: null,
106
+
107
+ // HTTP
108
+ axios: null,
109
+
110
+ // UI
111
+ 'framer-motion': null,
112
+ 'react-icons': null,
113
+ '@radix-ui/react-dialog': null,
114
+ '@radix-ui/react-dropdown-menu': null,
115
+ '@radix-ui/react-select': null,
116
+ '@radix-ui/react-slot': null,
117
+
118
+ // Styling
119
+ tailwindcss: null,
120
+
121
+ // Utils
122
+ 'date-fns': null,
123
+ lodash: null,
124
+ '@vueuse/core': null,
125
+ };
126
+
127
+ return await getLatestVersions(commonPackages);
128
+ }