@jatinmourya/ng-init 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/src/runner.js ADDED
@@ -0,0 +1,574 @@
1
+ import inquirer from 'inquirer';
2
+ import chalk from 'chalk';
3
+ import path from 'path';
4
+ import { displaySystemVersions, getNodeVersion, isNvmInstalled, switchNodeVersion, installNodeVersion, getInstalledNodeVersions } from './utils/version-checker.js';
5
+ import { getAngularVersions, getNodeRequirementsForAngular, getMajorVersions, getMinorVersionsForMajor, getPatchVersionsForMinor } from './utils/npm-search.js';
6
+ import { checkNodeCompatibility, displayCompatibilityStatus, findCompatibleVersions, getRecommendedNodeVersion, resolveLibraryVersions } from './utils/compatibility.js';
7
+ import { createAngularProject, installPackages, runNpmInstall, installNodeWithWinget, displayNvmInstallGuide } from './utils/installer.js';
8
+ import { interactiveLibrarySearch, simpleLibraryInput, askLibrarySearchPreference } from './utils/prompt-handler.js';
9
+ import { PROJECT_TEMPLATES, LIBRARY_BUNDLES, CONFIG_PRESETS, PROJECT_STRUCTURE, GIT_CONFIG, DOC_TEMPLATES } from './templates/templates.js';
10
+ import { initGitRepo, createGitignore, createInitialCommit, createProjectFolders, createProjectFiles, createReadme, createChangelog, validateDirectoryName, ensureDirectory, updatePackageJsonScripts } from './utils/file-utils.js';
11
+ import { saveProfile, loadProfile, listProfiles, displayProfileInfo } from './utils/profile-manager.js';
12
+
13
+ export async function runCli() {
14
+ try {
15
+ // Display welcome banner
16
+ console.log(chalk.bold.cyan('\n╔════════════════════════════════════════════════╗'));
17
+ console.log(chalk.bold.cyan('║ Angular Project Automation CLI v1.0.0 ║'));
18
+ console.log(chalk.bold.cyan('╚════════════════════════════════════════════════╝\n'));
19
+
20
+ // Step 1: Display system versions
21
+ const systemVersions = await displaySystemVersions();
22
+
23
+ // Step 2: Check for saved profiles
24
+ const useProfile = await inquirer.prompt([
25
+ {
26
+ type: 'confirm',
27
+ name: 'use',
28
+ message: 'Would you like to use a saved profile?',
29
+ default: false
30
+ }
31
+ ]);
32
+
33
+ let config = {};
34
+
35
+ if (useProfile.use) {
36
+ const profiles = await listProfiles();
37
+
38
+ if (profiles.length === 0) {
39
+ console.log(chalk.yellow('No saved profiles found. Continuing with manual setup...\n'));
40
+ } else {
41
+ const profileAnswer = await inquirer.prompt([
42
+ {
43
+ type: 'list',
44
+ name: 'profile',
45
+ message: 'Select a profile:',
46
+ choices: profiles
47
+ }
48
+ ]);
49
+
50
+ const profile = await loadProfile(profileAnswer.profile);
51
+ displayProfileInfo(profileAnswer.profile, profile);
52
+
53
+ const confirmProfile = await inquirer.prompt([
54
+ {
55
+ type: 'confirm',
56
+ name: 'confirm',
57
+ message: 'Use this profile?',
58
+ default: true
59
+ }
60
+ ]);
61
+
62
+ if (confirmProfile.confirm) {
63
+ config = profile;
64
+ }
65
+ }
66
+ }
67
+
68
+ // Step 3: Select Angular version (if not from profile)
69
+ if (!config.angularVersion) {
70
+ console.log(chalk.bold.cyan('\n📦 Fetching Angular versions...\n'));
71
+ const angularVersions = await getAngularVersions();
72
+
73
+ if (angularVersions.versions.length === 0) {
74
+ console.log(chalk.red('Failed to fetch Angular versions. Please check your internet connection.'));
75
+ process.exit(1);
76
+ }
77
+
78
+ // Step 3.1: Select Major Version
79
+ const majorVersions = getMajorVersions(angularVersions.versions);
80
+ const majorChoices = majorVersions.map(major => {
81
+ const label = `Angular ${major}`;
82
+ // Check if this major version contains the latest
83
+ const isLatest = angularVersions.latest && angularVersions.latest.startsWith(`${major}.`);
84
+ return {
85
+ name: isLatest ? `${label} (latest)` : label,
86
+ value: major
87
+ };
88
+ });
89
+
90
+ const majorAnswer = await inquirer.prompt([
91
+ {
92
+ type: 'list',
93
+ name: 'major',
94
+ message: 'Select Angular major version:',
95
+ choices: majorChoices,
96
+ pageSize: 15
97
+ }
98
+ ]);
99
+
100
+ // Step 3.2: Select Minor Version
101
+ const minorVersions = getMinorVersionsForMajor(angularVersions.versions, majorAnswer.major);
102
+ const minorChoices = minorVersions.map(minor => ({
103
+ name: `v${minor}.x`,
104
+ value: minor
105
+ }));
106
+
107
+ const minorAnswer = await inquirer.prompt([
108
+ {
109
+ type: 'list',
110
+ name: 'minor',
111
+ message: `Select Angular ${majorAnswer.major} minor version:`,
112
+ choices: minorChoices,
113
+ pageSize: 15
114
+ }
115
+ ]);
116
+
117
+ // Step 3.3: Select Patch Version
118
+ const patchVersions = getPatchVersionsForMinor(angularVersions.versions, minorAnswer.minor);
119
+ const patchChoices = patchVersions.map(patch => {
120
+ let label = `v${patch}`;
121
+ if (patch === angularVersions.latest) label += ' (latest)';
122
+ if (patch === angularVersions.lts) label += ' (LTS)';
123
+ return { name: label, value: patch };
124
+ });
125
+
126
+ const patchAnswer = await inquirer.prompt([
127
+ {
128
+ type: 'list',
129
+ name: 'patch',
130
+ message: `Select Angular ${minorAnswer.minor} patch version:`,
131
+ choices: patchChoices,
132
+ pageSize: 15
133
+ }
134
+ ]);
135
+
136
+ config.angularVersion = patchAnswer.patch;
137
+ }
138
+
139
+ console.log(chalk.green(`\n✓ Selected Angular version: ${config.angularVersion}\n`));
140
+
141
+ // Step 4: Check Node.js compatibility
142
+ const nodeRequirement = await getNodeRequirementsForAngular(config.angularVersion);
143
+ const currentNodeVersion = await getNodeVersion();
144
+ const compatibility = checkNodeCompatibility(currentNodeVersion, nodeRequirement);
145
+
146
+ displayCompatibilityStatus(compatibility);
147
+
148
+ // Step 5: Handle Node version incompatibility
149
+ if (!compatibility.compatible) {
150
+ console.log(chalk.yellow('⚠️ Node.js version incompatibility detected!\n'));
151
+
152
+ const nvmInstalled = await isNvmInstalled();
153
+
154
+ if (nvmInstalled) {
155
+ console.log(chalk.cyan('✓ nvm detected on your system\n'));
156
+
157
+ const installedVersions = await getInstalledNodeVersions();
158
+ const compatibleInstalled = findCompatibleVersions(installedVersions, nodeRequirement);
159
+
160
+ if (compatibleInstalled.length > 0) {
161
+ console.log(chalk.green(`Found ${compatibleInstalled.length} compatible Node version(s) installed:\n`));
162
+
163
+ const switchAnswer = await inquirer.prompt([
164
+ {
165
+ type: 'list',
166
+ name: 'version',
167
+ message: 'Select Node version to switch to:',
168
+ choices: compatibleInstalled.map(v => ({ name: `v${v}`, value: v }))
169
+ }
170
+ ]);
171
+
172
+ console.log(chalk.cyan(`\nSwitching to Node.js v${switchAnswer.version}...\n`));
173
+ const switched = await switchNodeVersion(switchAnswer.version);
174
+
175
+ if (!switched) {
176
+ console.log(chalk.red('Failed to switch Node version. Please try manually.'));
177
+ process.exit(1);
178
+ }
179
+
180
+ console.log(chalk.green('✓ Node version switched successfully\n'));
181
+ } else {
182
+ console.log(chalk.yellow('No compatible Node versions installed.\n'));
183
+ const recommendedVersion = getRecommendedNodeVersion(nodeRequirement);
184
+
185
+ const installAnswer = await inquirer.prompt([
186
+ {
187
+ type: 'confirm',
188
+ name: 'install',
189
+ message: `Install Node.js v${recommendedVersion}?`,
190
+ default: true
191
+ }
192
+ ]);
193
+
194
+ if (installAnswer.install) {
195
+ const installed = await installNodeVersion(recommendedVersion);
196
+
197
+ if (!installed) {
198
+ console.log(chalk.red('Failed to install Node version.'));
199
+ process.exit(1);
200
+ }
201
+
202
+ console.log(chalk.green('✓ Node.js installed successfully\n'));
203
+ await switchNodeVersion(recommendedVersion);
204
+ } else {
205
+ console.log(chalk.red('Cannot proceed without compatible Node.js version.'));
206
+ process.exit(1);
207
+ }
208
+ }
209
+ } else {
210
+ console.log(chalk.yellow('⚠️ nvm is not installed on your system\n'));
211
+
212
+ const installChoice = await inquirer.prompt([
213
+ {
214
+ type: 'list',
215
+ name: 'method',
216
+ message: 'How would you like to proceed?',
217
+ choices: [
218
+ { name: 'Install nvm (Recommended)', value: 'nvm' },
219
+ { name: 'Install Node.js directly (Windows only)', value: 'direct' },
220
+ { name: 'Exit and install manually', value: 'exit' }
221
+ ]
222
+ }
223
+ ]);
224
+
225
+ if (installChoice.method === 'nvm') {
226
+ displayNvmInstallGuide();
227
+ console.log(chalk.yellow('\nPlease install nvm and run this CLI again.\n'));
228
+ process.exit(0);
229
+ } else if (installChoice.method === 'direct') {
230
+ if (process.platform !== 'win32') {
231
+ console.log(chalk.red('Direct installation is only supported on Windows.'));
232
+ process.exit(1);
233
+ }
234
+
235
+ const installed = await installNodeWithWinget('LTS');
236
+
237
+ if (!installed) {
238
+ console.log(chalk.red('Failed to install Node.js. Please install manually.'));
239
+ process.exit(1);
240
+ }
241
+
242
+ console.log(chalk.yellow('\nPlease restart your terminal and run this CLI again.\n'));
243
+ process.exit(0);
244
+ } else {
245
+ console.log(chalk.yellow('Exiting. Please install a compatible Node.js version manually.\n'));
246
+ process.exit(0);
247
+ }
248
+ }
249
+ }
250
+
251
+ // Step 6: Project configuration
252
+ if (!config.projectName) {
253
+ const projectAnswer = await inquirer.prompt([
254
+ {
255
+ type: 'input',
256
+ name: 'name',
257
+ message: 'Enter project name:',
258
+ validate: (value) => {
259
+ if (!value) return 'Project name is required';
260
+ const validation = validateDirectoryName(value);
261
+ return validation === true ? true : validation;
262
+ }
263
+ }
264
+ ]);
265
+
266
+ config.projectName = projectAnswer.name;
267
+ }
268
+
269
+ // Step 7: Project location
270
+ if (!config.location) {
271
+ const locationAnswer = await inquirer.prompt([
272
+ {
273
+ type: 'list',
274
+ name: 'location',
275
+ message: 'Where would you like to create the project?',
276
+ choices: [
277
+ { name: 'Current directory', value: 'current' },
278
+ { name: 'Specify custom directory', value: 'custom' }
279
+ ]
280
+ }
281
+ ]);
282
+
283
+ if (locationAnswer.location === 'custom') {
284
+ const customPath = await inquirer.prompt([
285
+ {
286
+ type: 'input',
287
+ name: 'path',
288
+ message: 'Enter directory path:',
289
+ default: process.cwd()
290
+ }
291
+ ]);
292
+
293
+ config.location = customPath.path;
294
+ } else {
295
+ config.location = process.cwd();
296
+ }
297
+ }
298
+
299
+ const projectPath = path.join(config.location, config.projectName);
300
+
301
+ // Step 8: Select template (if not from profile)
302
+ if (!config.template) {
303
+ const templateAnswer = await inquirer.prompt([
304
+ {
305
+ type: 'list',
306
+ name: 'template',
307
+ message: 'Select project template:',
308
+ choices: [
309
+ ...Object.entries(PROJECT_TEMPLATES).map(([key, template]) => ({
310
+ name: `${template.name} - ${template.description}`,
311
+ value: key
312
+ })),
313
+ { name: 'Custom (configure manually)', value: 'custom' }
314
+ ]
315
+ }
316
+ ]);
317
+
318
+ config.template = templateAnswer.template;
319
+
320
+ if (config.template === 'custom') {
321
+ const customOptions = await inquirer.prompt([
322
+ {
323
+ type: 'confirm',
324
+ name: 'routing',
325
+ message: 'Enable routing?',
326
+ default: true
327
+ },
328
+ {
329
+ type: 'list',
330
+ name: 'style',
331
+ message: 'Select stylesheet format:',
332
+ choices: ['css', 'scss', 'sass', 'less']
333
+ },
334
+ {
335
+ type: 'confirm',
336
+ name: 'strict',
337
+ message: 'Enable strict mode?',
338
+ default: true
339
+ },
340
+ {
341
+ type: 'confirm',
342
+ name: 'standalone',
343
+ message: 'Use standalone components?',
344
+ default: false
345
+ }
346
+ ]);
347
+
348
+ config.options = customOptions;
349
+ } else {
350
+ config.options = PROJECT_TEMPLATES[config.template].options;
351
+ }
352
+ }
353
+
354
+ // Step 9: Library selection (if not from profile)
355
+ if (!config.libraries) {
356
+ const libraryMethod = await askLibrarySearchPreference();
357
+ config.libraries = [];
358
+
359
+ if (libraryMethod === 'interactive') {
360
+ config.libraries = await interactiveLibrarySearch(config.angularVersion);
361
+ } else if (libraryMethod === 'manual') {
362
+ config.libraries = await simpleLibraryInput(config.angularVersion);
363
+ } else if (libraryMethod === 'bundles') {
364
+ const bundleAnswer = await inquirer.prompt([
365
+ {
366
+ type: 'checkbox',
367
+ name: 'bundles',
368
+ message: 'Select library bundles:',
369
+ choices: Object.entries(LIBRARY_BUNDLES).map(([key, bundle]) => ({
370
+ name: `${bundle.name} - ${bundle.description}`,
371
+ value: key
372
+ }))
373
+ }
374
+ ]);
375
+
376
+ for (const bundleKey of bundleAnswer.bundles) {
377
+ const bundle = LIBRARY_BUNDLES[bundleKey];
378
+ config.libraries.push(...bundle.packages);
379
+ }
380
+ }
381
+
382
+ // Add template-specific libraries
383
+ if (config.template !== 'custom' && PROJECT_TEMPLATES[config.template].packages) {
384
+ const templateLibs = PROJECT_TEMPLATES[config.template].packages.map(name => ({
385
+ name,
386
+ version: 'latest'
387
+ }));
388
+ config.libraries.push(...templateLibs);
389
+ }
390
+ }
391
+
392
+ // Step 10: Additional features
393
+ const featuresAnswer = await inquirer.prompt([
394
+ {
395
+ type: 'checkbox',
396
+ name: 'features',
397
+ message: 'Select additional features:',
398
+ choices: [
399
+ { name: 'Git initialization', value: 'git', checked: true },
400
+ { name: 'Create project structure', value: 'structure', checked: true },
401
+ { name: 'Generate README.md', value: 'readme', checked: true },
402
+ { name: 'Generate CHANGELOG.md', value: 'changelog', checked: false },
403
+ { name: 'ESLint + Prettier setup', value: 'eslint', checked: false },
404
+ { name: 'Husky pre-commit hooks', value: 'husky', checked: false }
405
+ ]
406
+ }
407
+ ]);
408
+
409
+ config.features = featuresAnswer.features;
410
+
411
+ // Step 11: Save profile option
412
+ const saveProfileAnswer = await inquirer.prompt([
413
+ {
414
+ type: 'confirm',
415
+ name: 'save',
416
+ message: 'Save this configuration as a profile?',
417
+ default: false
418
+ }
419
+ ]);
420
+
421
+ if (saveProfileAnswer.save) {
422
+ const profileNameAnswer = await inquirer.prompt([
423
+ {
424
+ type: 'input',
425
+ name: 'name',
426
+ message: 'Enter profile name:',
427
+ validate: (value) => value ? true : 'Profile name is required'
428
+ }
429
+ ]);
430
+
431
+ await saveProfile(profileNameAnswer.name, config);
432
+ }
433
+
434
+ // Step 12: Confirm and create project
435
+ console.log(chalk.bold.cyan('\n📋 Project Configuration Summary\n'));
436
+ console.log(chalk.gray('━'.repeat(50)));
437
+ console.log(chalk.white('Project Name: ') + chalk.green(config.projectName));
438
+ console.log(chalk.white('Location: ') + chalk.cyan(projectPath));
439
+ console.log(chalk.white('Angular Version: ') + chalk.green(config.angularVersion));
440
+ console.log(chalk.white('Template: ') + chalk.cyan(config.template));
441
+ console.log(chalk.white('Libraries: ') + chalk.cyan(config.libraries.length));
442
+ console.log(chalk.white('Features: ') + chalk.cyan(config.features.join(', ')));
443
+ console.log(chalk.gray('━'.repeat(50)) + '\n');
444
+
445
+ const confirmAnswer = await inquirer.prompt([
446
+ {
447
+ type: 'confirm',
448
+ name: 'confirm',
449
+ message: 'Create project with this configuration?',
450
+ default: true
451
+ }
452
+ ]);
453
+
454
+ if (!confirmAnswer.confirm) {
455
+ console.log(chalk.yellow('Project creation cancelled.\n'));
456
+ process.exit(0);
457
+ }
458
+
459
+ // Step 13: Create Angular project
460
+ console.log(chalk.bold.cyan('\n🚀 Creating Angular project...\n'));
461
+
462
+ const createOptions = {
463
+ ...config.options,
464
+ skipInstall: true
465
+ };
466
+
467
+ const created = await createAngularProject(config.projectName, config.angularVersion, createOptions);
468
+
469
+ if (!created) {
470
+ console.log(chalk.red('Failed to create Angular project.'));
471
+ process.exit(1);
472
+ }
473
+
474
+ // Step 14: Install libraries
475
+ if (config.libraries.length > 0) {
476
+ console.log(chalk.bold.cyan('\n📦 Installing additional libraries...\n'));
477
+
478
+ // Resolve library versions for compatibility with Angular version
479
+ const resolvedLibraries = resolveLibraryVersions(config.libraries, config.angularVersion);
480
+
481
+ // Show adjusted versions if any
482
+ const adjusted = resolvedLibraries.filter(lib => lib.adjusted);
483
+ if (adjusted.length > 0) {
484
+ console.log(chalk.yellow('⚠️ Adjusted library versions for Angular compatibility:\n'));
485
+ adjusted.forEach(lib => {
486
+ console.log(chalk.gray(` ${lib.name}: ${lib.originalVersion} → ${lib.version}`));
487
+ });
488
+ console.log('');
489
+ }
490
+
491
+ const librarySpecs = resolvedLibraries.map(lib =>
492
+ lib.version === 'latest' ? lib.name : `${lib.name}@${lib.version}`
493
+ );
494
+
495
+ await installPackages(librarySpecs, projectPath);
496
+ }
497
+
498
+ // Step 15: Run npm install
499
+ console.log(chalk.bold.cyan('\n📥 Installing dependencies...\n'));
500
+ await runNpmInstall(projectPath);
501
+
502
+ // Step 16: Create project structure
503
+ if (config.features.includes('structure')) {
504
+ console.log(chalk.bold.cyan('\n📁 Creating project structure...\n'));
505
+ await createProjectFolders(projectPath, PROJECT_STRUCTURE.standard.folders);
506
+ await createProjectFiles(projectPath, PROJECT_STRUCTURE.standard.files);
507
+ }
508
+
509
+ // Step 17: Initialize Git
510
+ if (config.features.includes('git')) {
511
+ console.log(chalk.bold.cyan('\n🔧 Initializing Git repository...\n'));
512
+ await initGitRepo(projectPath);
513
+ await createGitignore(projectPath, GIT_CONFIG.gitignore);
514
+ }
515
+
516
+ // Step 18: Generate documentation
517
+ if (config.features.includes('readme')) {
518
+ console.log(chalk.bold.cyan('\n📝 Generating README.md...\n'));
519
+ const readmeContent = DOC_TEMPLATES.readme(config.projectName, 'An Angular application created with Angular Project Automator');
520
+ await createReadme(projectPath, readmeContent);
521
+ }
522
+
523
+ if (config.features.includes('changelog')) {
524
+ await createChangelog(projectPath, DOC_TEMPLATES.changelog);
525
+ }
526
+
527
+ // Step 19: Setup ESLint
528
+ if (config.features.includes('eslint')) {
529
+ console.log(chalk.bold.cyan('\n🔧 Setting up ESLint + Prettier...\n'));
530
+ const eslintPackages = CONFIG_PRESETS.eslint.packages.map(p => p);
531
+ await installPackages(eslintPackages, projectPath, true);
532
+ await createProjectFiles(projectPath, CONFIG_PRESETS.eslint.files);
533
+ }
534
+
535
+ // Step 20: Setup Husky
536
+ if (config.features.includes('husky')) {
537
+ console.log(chalk.bold.cyan('\n🐶 Setting up Husky...\n'));
538
+ const huskyPackages = CONFIG_PRESETS.husky.devPackages.map(p => p);
539
+ await installPackages(huskyPackages, projectPath, true);
540
+ await updatePackageJsonScripts(projectPath, CONFIG_PRESETS.husky.scripts);
541
+ }
542
+
543
+ // Step 21: Create initial commit
544
+ if (config.features.includes('git')) {
545
+ console.log(chalk.bold.cyan('\n📝 Creating initial commit...\n'));
546
+ await createInitialCommit(projectPath, GIT_CONFIG.initialCommitMessage);
547
+ }
548
+
549
+ // Step 22: Display success message
550
+ console.log(chalk.bold.green('\n✅ Project created successfully! 🎉\n'));
551
+ console.log(chalk.bold.cyan('📊 Next Steps:\n'));
552
+ console.log(chalk.gray('━'.repeat(50)));
553
+ console.log(chalk.white('1. ') + chalk.cyan(`cd ${config.projectName}`));
554
+ console.log(chalk.white('2. ') + chalk.cyan('ng serve'));
555
+ console.log(chalk.white('3. ') + chalk.cyan('Open http://localhost:4200 in your browser'));
556
+ console.log(chalk.gray('━'.repeat(50)));
557
+
558
+ console.log(chalk.bold.cyan('\n💡 Useful Commands:\n'));
559
+ console.log(chalk.gray(' ng generate component <name> ') + chalk.white('Create a component'));
560
+ console.log(chalk.gray(' ng generate service <name> ') + chalk.white('Create a service'));
561
+ console.log(chalk.gray(' ng build ') + chalk.white('Build for production'));
562
+ console.log(chalk.gray(' ng test ') + chalk.white('Run unit tests'));
563
+ console.log(chalk.gray(' ng help ') + chalk.white('Get more help\n'));
564
+
565
+ console.log(chalk.bold.green('Happy coding! 🚀\n'));
566
+
567
+ } catch (err) {
568
+ console.error(chalk.red('\n❌ Error:'), err.message);
569
+ if (err.stack) {
570
+ console.error(chalk.gray(err.stack));
571
+ }
572
+ process.exit(1);
573
+ }
574
+ }