package-installer-cli 1.0.0 → 1.2.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 (47) hide show
  1. checksums.yaml +4 -4
  2. data/CHANGELOG.md +8 -0
  3. data/dist/commands/add.js +7 -49
  4. data/dist/index.js +17 -10
  5. data/dist/utils/banner.js +3 -1
  6. data/dist/utils/cacheUtils.js +1 -1
  7. data/dist/utils/dashboard.js +1 -1
  8. data/dist/utils/dependencyInstaller.js +2 -46
  9. data/dist/utils/featureInstaller.js +158 -105
  10. data/dist/utils/languageConfig.js +169 -300
  11. data/dist/utils/pathResolver.js +179 -0
  12. data/dist/utils/prompts.js +5 -14
  13. data/dist/utils/templateCreator.js +3 -3
  14. data/dist/utils/templateResolver.js +8 -17
  15. data/dist/utils/types.js +2 -2
  16. data/dist/utils/ui.js +2 -2
  17. data/dist/utils/utils.js +20 -1
  18. data/lib/package_installer_cli.rb +1 -1
  19. data/templates/django/django-full-stack-template/django-project-template/deployment/docker/build.sh +0 -0
  20. data/templates/django/django-full-stack-template/django-project-template/deployment/docker/manage.py +0 -0
  21. data/templates/django/django-full-stack-template/django-project-template/src/compile-locale.sh +0 -0
  22. data/templates/django/django-full-stack-template/django-project-template/src/compile-sass.sh +0 -0
  23. data/templates/django/django-full-stack-template/django-project-template/src/manage.py +0 -0
  24. data/templates/django/django-inertia-svelte-template/django-inertia-svelte-template-starter/manage.py +0 -0
  25. data/templates/flask/flask-cookiecutter-advance-template/cookiecutter-docker.sh +0 -0
  26. data/templates/flask/flask-cookiecutter-advance-template/{{cookiecutter.app_name}}/autoapp.py +0 -0
  27. data/templates/flask/flask-project-template/apply.sh +0 -0
  28. data/templates/react-native/typescript/template/android/gradlew +0 -0
  29. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/bundle +0 -0
  30. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/dev +0 -0
  31. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/docker-entrypoint +0 -0
  32. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/rails +0 -0
  33. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/rake +0 -0
  34. data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/setup +0 -0
  35. data/templates/ruby/ruby-on-rails-apis-template/bin/bundle +0 -0
  36. data/templates/ruby/ruby-on-rails-apis-template/bin/rails +0 -0
  37. data/templates/ruby/ruby-on-rails-apis-template/bin/rake +0 -0
  38. data/templates/ruby/ruby-on-rails-apis-template/bin/setup +0 -0
  39. data/templates/ruby/ruby-on-rails-apis-template/bin/spring +0 -0
  40. data/templates/ruby/ruby-on-rails-apis-template/bin/update +0 -0
  41. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/bundle +0 -0
  42. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/dev +0 -0
  43. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/importmap +0 -0
  44. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/rails +0 -0
  45. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/rake +0 -0
  46. data/templates/ruby/ruby-on-rails-boilerplate-template/bin/setup +0 -0
  47. metadata +6 -5
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 665501d841b7bce60cdb5917c5aa517e05236932dee2966958dc444fe0bf6108
4
- data.tar.gz: ab2a5a999c255824b7c903ed2b46c0018e93c3bd08010d46e4970e43dfca80c3
3
+ metadata.gz: 7f432378951e281eb37210295d7894abbf072093bd9a2e6099f1d3219ad45eb0
4
+ data.tar.gz: 0e9edf8310281ce583765615d8d41f1897b7e0fab8e6cc3dbbfa405d39a63cc9
5
5
  SHA512:
6
- metadata.gz: 41e9956c319e5df07d8fd65c81129fb00c65c4d4987d706b35e035c63e70b74a9b7f0172a36ee2d278da979b4bd683b3471a824e831afa7250cec4464abf69ab
7
- data.tar.gz: c8fc5ea386fbc9e0eb4c3ebf7a7601db1ea47887378f06c6d4134e615356f920a27e038cdeb67e63d6d41692ed605382f530e0642015120048828d97cf2a81fe
6
+ metadata.gz: b0490199b16d2f27b1513fc9360895c02cb9a1a2fc58a4cddc5611c20afa21c8ef6e609d99b4dc7271a19de0ea21c1275127839d4b56b26b91e0554cff66c296
7
+ data.tar.gz: 6e7cec10cae328695c8fb9328797f5efc46ced7e04b0bb99dffe40ccbb887bc9ee495277b2ac49e5196e88f98e6a71a2cec823ad4ec692ee78424ce5cc3b5d53
data/CHANGELOG.md CHANGED
@@ -5,6 +5,14 @@ All notable changes to the Package Installer CLI Ruby gem will be documented in
5
5
  The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
6
6
  and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
7
 
8
+ ## [1.1.0] - 2025-09-23
9
+
10
+ ### Updated
11
+ - Version bump to 1.1.0
12
+ - Improved gem packaging and file inclusion
13
+ - Enhanced security with comprehensive .gitignore rules
14
+ - Updated documentation and README
15
+
8
16
  ## [1.0.0] - 2025-09-20
9
17
 
10
18
  ### Added
data/dist/commands/add.js CHANGED
@@ -4,50 +4,10 @@ import gradient from 'gradient-string';
4
4
  import boxen from 'boxen';
5
5
  import path from 'path';
6
6
  import fs from 'fs-extra';
7
- import { fileURLToPath } from 'url';
8
- import { addFeature, detectProjectStack, SUPPORTED_FEATURES, getCliRootPath } from '../utils/featureInstaller.js';
7
+ import { addFeature, detectProjectStack, SUPPORTED_FEATURES } from '../utils/featureInstaller.js';
9
8
  import { historyManager } from '../utils/historyManager.js';
10
9
  import { getCachedProject, cacheProjectData } from '../utils/cacheManager.js';
11
- /**
12
- * Resolve CLI installation paths for both local and global installations
13
- */
14
- function resolveCLIPath() {
15
- // Get the current file's directory
16
- const currentFile = fileURLToPath(import.meta.url);
17
- const currentDir = path.dirname(currentFile);
18
- // Try to find package root by looking for package.json
19
- let packageRoot = currentDir;
20
- while (packageRoot !== path.dirname(packageRoot)) {
21
- const packageJsonPath = path.join(packageRoot, 'package.json');
22
- if (fs.existsSync(packageJsonPath)) {
23
- try {
24
- const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf-8'));
25
- if (packageJson.name === '@0xshariq/package-installer') {
26
- return packageRoot;
27
- }
28
- }
29
- catch (error) {
30
- // Continue searching
31
- }
32
- }
33
- packageRoot = path.dirname(packageRoot);
34
- }
35
- // Fallback: use current working directory if we're in development
36
- if (fs.existsSync(path.join(process.cwd(), 'features'))) {
37
- return process.cwd();
38
- }
39
- // Final fallback: try node_modules resolution
40
- try {
41
- const nodeModulesPath = path.join(currentDir, '../../../');
42
- if (fs.existsSync(path.join(nodeModulesPath, 'features'))) {
43
- return nodeModulesPath;
44
- }
45
- }
46
- catch (error) {
47
- // Ignore
48
- }
49
- return process.cwd();
50
- }
10
+ import { getFeaturesJsonPath, getFeaturesPath } from '../utils/pathResolver.js';
51
11
  /**
52
12
  * Helper function to capitalize strings
53
13
  */
@@ -59,13 +19,12 @@ function capitalize(str) {
59
19
  */
60
20
  function getFeaturesConfig() {
61
21
  try {
62
- // Get the CLI root path
63
- const cliRoot = getCliRootPath();
64
- const featuresPath = path.join(cliRoot, 'features', 'features.json');
22
+ // Use the centralized path resolver
23
+ const featuresPath = getFeaturesJsonPath();
65
24
  if (fs.existsSync(featuresPath)) {
66
25
  return JSON.parse(fs.readFileSync(featuresPath, 'utf-8'));
67
26
  }
68
- console.warn(chalk.yellow('⚠️ features.json not found, using fallback'));
27
+ console.warn(chalk.yellow(`⚠️ features.json not found at: ${featuresPath}`));
69
28
  return { features: {} };
70
29
  }
71
30
  catch (error) {
@@ -404,15 +363,14 @@ export async function addCommand(feature, provider, options = {}) {
404
363
  console.log(chalk.green(`✅ Detected ${projectInfo.framework} project (${projectInfo.projectLanguage || projectInfo.language})`));
405
364
  }
406
365
  // Validate that framework features exist in features directory
407
- const cliRoot = getCliRootPath();
408
- const frameworkFeaturesPath = path.join(cliRoot, 'features');
366
+ const frameworkFeaturesPath = getFeaturesPath();
409
367
  if (!await fs.pathExists(frameworkFeaturesPath)) {
410
368
  console.log(chalk.red('❌ Features directory not found'));
411
369
  console.log(chalk.yellow('💡 Make sure you\'re running this from the Package Installer CLI root directory'));
412
370
  return;
413
371
  }
414
372
  // Load features configuration
415
- const featuresConfigPath = path.join(frameworkFeaturesPath, 'features.json');
373
+ const featuresConfigPath = getFeaturesJsonPath();
416
374
  if (!await fs.pathExists(featuresConfigPath)) {
417
375
  console.log(chalk.red('❌ Features configuration not found'));
418
376
  return;
data/dist/index.js CHANGED
@@ -18,17 +18,24 @@ import { deployCommand } from './commands/deploy.js';
18
18
  import { cleanCommand, showCleanHelp } from './commands/clean.js';
19
19
  import { environmentCommand, showEnvironmentHelp } from './commands/env.js';
20
20
  import { doctorCommand, showDoctorHelp } from './commands/doctor.js';
21
- // Import utilities
22
- import { showBanner } from './utils/ui.js';
23
21
  import { initializeCache } from './utils/cacheManager.js';
22
+ import { displayBanner, displayCommandBanner } from './utils/banner.js';
23
+ import { getPackageJsonPath } from './utils/pathResolver.js';
24
24
  // Get current file directory for ESM
25
25
  const __filename = fileURLToPath(import.meta.url);
26
26
  const __dirname = dirname(__filename);
27
- // Load package.json to get version
28
- let packageJsonPath = join(__dirname, '../package.json');
29
- // Check if we're running from dist folder
30
- if (__dirname.includes('/dist/')) {
31
- packageJsonPath = join(__dirname, '../../package.json');
27
+ // Load package.json to get version using improved path resolution
28
+ let packageJsonPath;
29
+ try {
30
+ packageJsonPath = getPackageJsonPath();
31
+ }
32
+ catch (error) {
33
+ // Fallback to the old method if getPackageJsonPath fails
34
+ packageJsonPath = join(__dirname, '../package.json');
35
+ // Check if we're running from dist folder
36
+ if (__dirname.includes('/dist/')) {
37
+ packageJsonPath = join(__dirname, '../../package.json');
38
+ }
32
39
  }
33
40
  const packageJson = JSON.parse(readFileSync(packageJsonPath, 'utf8'));
34
41
  const VERSION = packageJson.version;
@@ -74,7 +81,7 @@ program
74
81
  })
75
82
  .action(async (projectName) => {
76
83
  try {
77
- showBanner();
84
+ displayCommandBanner('create', 'Create a new project from templates');
78
85
  await createProject(projectName);
79
86
  }
80
87
  catch (error) {
@@ -206,7 +213,7 @@ program
206
213
  })
207
214
  .action(async (options) => {
208
215
  try {
209
- showBanner();
216
+ displayCommandBanner('clean', 'Clean development artifacts and caches');
210
217
  await cleanCommand(options);
211
218
  }
212
219
  catch (error) {
@@ -336,7 +343,7 @@ program.on('--help', () => {
336
343
  });
337
344
  // ENHANCED DEFAULT BEHAVIOR - Beautiful banner and help when no command provided
338
345
  if (process.argv.length === 2) {
339
- showBanner();
346
+ displayBanner();
340
347
  console.log('\n' + boxen(chalk.white('Welcome to Package Installer CLI! 👋') + '\n\n' +
341
348
  chalk.hex('#00d2d3')('🚀 Ready to build something amazing?') + '\n\n' +
342
349
  chalk.hex('#95afc0')('Start with these popular commands:') + '\n\n' +
data/dist/utils/banner.js CHANGED
@@ -5,6 +5,7 @@
5
5
  import chalk from 'chalk';
6
6
  import gradient from 'gradient-string';
7
7
  import boxen from 'boxen';
8
+ import { getPackageVersion } from './utils.js';
8
9
  /**
9
10
  * Generate the main CLI banner with gradient colors
10
11
  */
@@ -40,7 +41,8 @@ export function generateBanner() {
40
41
  * Generate version info banner
41
42
  */
42
43
  export function generateVersionBanner() {
43
- return boxen(chalk.hex('#00d2d3')('📦 Version: ') + chalk.hex('#ffa502')('v3.2.0') +
44
+ const version = getPackageVersion();
45
+ return boxen(chalk.hex('#00d2d3')('📦 Version: ') + chalk.hex('#ffa502')(`v${version}`) +
44
46
  chalk.hex('#95afc0')(' • ') + chalk.hex('#00d2d3')('🎯 Frameworks: ') + chalk.hex('#ffa502')('12+') +
45
47
  chalk.hex('#95afc0')(' • ') + chalk.hex('#00d2d3')('📋 Templates: ') + chalk.hex('#ffa502')('50+') +
46
48
  chalk.hex('#95afc0')(' • ') + chalk.hex('#00d2d3')('⚡ Status: ') + chalk.hex('#10ac84')('Ready to scaffold!'), {
@@ -1,5 +1,5 @@
1
1
  /**
2
- * Enhanced Cache Utility for Package Installer CLI v3.0.0
2
+ * Enhanced Cache Utility for Package Installer CLI v3.2.0
3
3
  * Advanced caching system with compression, encryption, and smart invalidation
4
4
  */
5
5
  import fs from 'fs-extra';
@@ -35,7 +35,7 @@ export function createBanner(title = 'Package Installer CLI') {
35
35
  console.log(banner);
36
36
  // Add tagline with updated branding
37
37
  const tagline = chalk.hex('#00d2d3')('🚀 Advanced Project Analytics Dashboard');
38
- const version = chalk.hex('#95afc0')('v3.0.0');
38
+ const version = chalk.hex('#95afc0')('v3.2.0');
39
39
  const author = chalk.hex('#ffa502')('by @0xshariq');
40
40
  const centered = `${tagline} ${version} ${author}`;
41
41
  const padding = Math.max(0, Math.floor(((process.stdout.columns || 80) - centered.length) / 2));
@@ -1,6 +1,6 @@
1
1
  /**
2
- * Enhanced Multi-language dependency installer utility for Package Installer CLI v3.0.0
3
- * Supports Node.js, Rust, Python, Go, Ruby, PHP, Java, C#, Swift, Dart/Flutter with modern features
2
+ * Enhanced Multi-language dependency installer utility for Package Installer CLI v3.2.0
3
+ * Supports Node.js, Python, Rust, Go, Ruby, and more with intelligent package management
4
4
  */
5
5
  import { exec } from 'child_process';
6
6
  import { promisify } from 'util';
@@ -332,28 +332,6 @@ function extractInstalledPackages(output, language) {
332
332
  });
333
333
  }
334
334
  break;
335
- case 'php':
336
- // Parse composer output
337
- const phpMatches = output.match(/Installing (.+?) \(/gi);
338
- if (phpMatches) {
339
- phpMatches.forEach(match => {
340
- const pkg = match.replace(/Installing\s+/, '').replace(/\s+\(.*/, '');
341
- if (pkg)
342
- packages.push(pkg);
343
- });
344
- }
345
- break;
346
- case 'java':
347
- // Parse maven output
348
- const javaMatches = output.match(/Downloaded from .+?: (.+?) \(/gi);
349
- if (javaMatches) {
350
- javaMatches.forEach(match => {
351
- const pkg = match.match(/: (.+?) \(/)?.[1];
352
- if (pkg)
353
- packages.push(pkg);
354
- });
355
- }
356
- break;
357
335
  case 'ruby':
358
336
  // Parse bundler output
359
337
  const rubyMatches = output.match(/Installing (.+?) \(/gi);
@@ -365,17 +343,6 @@ function extractInstalledPackages(output, language) {
365
343
  });
366
344
  }
367
345
  break;
368
- case 'dotnet':
369
- // Parse dotnet output
370
- const dotnetMatches = output.match(/PackageReference for package '(.+?)'/gi);
371
- if (dotnetMatches) {
372
- dotnetMatches.forEach(match => {
373
- const pkg = match.match(/'(.+?)'/)?.[1];
374
- if (pkg)
375
- packages.push(pkg);
376
- });
377
- }
378
- break;
379
346
  default:
380
347
  // Generic extraction
381
348
  packages.push('dependencies');
@@ -443,20 +410,9 @@ export async function installPackages(projectPath, language, packages, options =
443
410
  case 'go':
444
411
  command = `go get ${packages.join(' ')}`;
445
412
  break;
446
- case 'php':
447
- const composerFlags = isDev ? '--dev' : '';
448
- command = `composer require ${composerFlags} ${packages.join(' ')}`;
449
- break;
450
- case 'java':
451
- // For Maven, we'd typically need to edit pom.xml, but for simplicity:
452
- command = `mvn dependency:get -Dartifact=${packages[0]}`;
453
- break;
454
413
  case 'ruby':
455
414
  command = `bundle add ${packages.join(' ')}`;
456
415
  break;
457
- case 'dotnet':
458
- command = `dotnet add package ${packages.join(' ')}`;
459
- break;
460
416
  default:
461
417
  throw new Error(`Unsupported language: ${language}`);
462
418
  }
@@ -10,6 +10,7 @@ import ora from 'ora';
10
10
  import { installPackages } from './dependencyInstaller.js';
11
11
  import { detectLanguageFromFiles } from './languageConfig.js';
12
12
  import { cacheProjectData, getCachedProject } from './cacheManager.js';
13
+ import { getCliRootPath, getFeaturesJsonPath } from './pathResolver.js';
13
14
  // Get the directory of this file for proper path resolution
14
15
  const __filename = fileURLToPath(import.meta.url);
15
16
  const __dirname = dirname(__filename);
@@ -20,15 +21,14 @@ let SUPPORTED_FEATURES = {};
20
21
  */
21
22
  async function loadFeatures() {
22
23
  try {
23
- // Get CLI installation directory
24
- const __filename = fileURLToPath(import.meta.url);
25
- const __dirname = path.dirname(__filename);
26
- const cliDir = path.resolve(__dirname, '..', '..');
27
- // Load directly from file system (simplified approach)
28
- const featuresPath = path.join(cliDir, 'features', 'features.json');
24
+ // Get CLI installation directory using the centralized path resolver
25
+ const featuresPath = getFeaturesJsonPath();
29
26
  if (await fs.pathExists(featuresPath)) {
30
27
  const featuresData = await fs.readJson(featuresPath);
31
- SUPPORTED_FEATURES = featuresData.features;
28
+ SUPPORTED_FEATURES = featuresData.features || featuresData;
29
+ }
30
+ else {
31
+ console.warn(chalk.yellow(`⚠️ Features file not found at: ${featuresPath}`));
32
32
  }
33
33
  }
34
34
  catch (error) {
@@ -39,11 +39,13 @@ async function loadFeatures() {
39
39
  await loadFeatures();
40
40
  // Export for use in other modules
41
41
  export { SUPPORTED_FEATURES };
42
+ // Re-export path utilities for backward compatibility
43
+ export { getCliRootPath } from './pathResolver.js';
42
44
  /**
43
45
  * Detect the current project's framework and language with improved logic
44
46
  */
45
47
  /**
46
- * Detect if a Next.js project uses src folder structure
48
+ * Detect if a Next.js project uses src folder structure (Next.js only)
47
49
  */
48
50
  async function detectNextjsSrcStructure(projectPath) {
49
51
  try {
@@ -52,17 +54,17 @@ async function detectNextjsSrcStructure(projectPath) {
52
54
  if (!await fs.pathExists(srcPath)) {
53
55
  return false;
54
56
  }
55
- // Check for app directory in src (App Router)
57
+ // Check for Next.js App Router (app directory in src)
56
58
  const srcAppPath = path.join(srcPath, 'app');
57
59
  if (await fs.pathExists(srcAppPath)) {
58
60
  return true;
59
61
  }
60
- // Check for pages directory in src (Pages Router)
62
+ // Check for Next.js Pages Router (pages directory in src)
61
63
  const srcPagesPath = path.join(srcPath, 'pages');
62
64
  if (await fs.pathExists(srcPagesPath)) {
63
65
  return true;
64
66
  }
65
- // Check for components directory in src
67
+ // Check for components directory in src (common pattern)
66
68
  const srcComponentsPath = path.join(srcPath, 'components');
67
69
  if (await fs.pathExists(srcComponentsPath)) {
68
70
  return true;
@@ -74,45 +76,35 @@ async function detectNextjsSrcStructure(projectPath) {
74
76
  }
75
77
  }
76
78
  /**
77
- * Adjust file path for Next.js src folder structure
79
+ * Adjust file path for Next.js src folder structure (Next.js only)
80
+ * Dynamically places files in src/ folder based on their path structure
78
81
  */
79
- function adjustNextjsFilePath(filePath, hasSrcFolder, projectPath) {
80
- // Files that should be placed in src folder when src structure is used
81
- const srcFolderFiles = [
82
- 'app/',
83
- 'pages/',
84
- 'components/',
85
- 'lib/',
86
- 'utils/',
87
- 'hooks/',
88
- 'context/',
89
- 'types/',
90
- 'styles/' // Only component-specific styles, not global ones
91
- ];
92
- // Files that should always be in root regardless of src folder
82
+ function adjustNextjsSrcFilePath(filePath, hasSrcFolder, projectPath) {
83
+ // If project doesn't use src folder, return original path
84
+ if (!hasSrcFolder) {
85
+ return path.join(projectPath, filePath);
86
+ }
87
+ // Files that should ALWAYS be in root regardless of src folder
93
88
  const rootOnlyFiles = [
94
- 'middleware.ts',
95
- 'middleware.js',
96
- 'next.config.js',
97
- 'next.config.mjs',
98
89
  '.env',
99
90
  '.env.local',
100
91
  '.env.example',
101
92
  'package.json',
93
+ 'next.config.js',
94
+ 'next.config.mjs',
102
95
  'tailwind.config.js',
103
96
  'tailwind.config.ts',
104
- 'postcss.config.js'
97
+ 'postcss.config.js',
98
+ 'middleware.ts',
99
+ 'middleware.js'
105
100
  ];
106
- // Check if this file should always be in root
107
101
  const fileName = path.basename(filePath);
108
- if (rootOnlyFiles.includes(fileName) || filePath.includes('public/')) {
109
- return filePath;
110
- }
111
- // If project has src folder and this file should be in src
112
- if (hasSrcFolder && srcFolderFiles.some(prefix => filePath.startsWith(prefix))) {
113
- return path.join('src', filePath);
102
+ // Check if this file should always be in root
103
+ if (rootOnlyFiles.includes(fileName) || filePath.startsWith('public/')) {
104
+ return path.join(projectPath, filePath);
114
105
  }
115
- return filePath;
106
+ // For all other files, place them in src/ folder if src structure is used
107
+ return path.join(projectPath, 'src', filePath);
116
108
  }
117
109
  export async function detectProjectStack(projectPath) {
118
110
  try {
@@ -181,7 +173,7 @@ export async function detectProjectStack(projectPath) {
181
173
  else if (dependencies['@remix-run/react']) {
182
174
  framework = 'remixjs';
183
175
  }
184
- // For non-Next.js projects, simple src folder check
176
+ // For other frameworks, simple src folder check
185
177
  if (framework !== 'nextjs' && !hasSrcFolder) {
186
178
  hasSrcFolder = await fs.pathExists(path.join(projectPath, 'src'));
187
179
  }
@@ -319,9 +311,9 @@ async function processFeatureFile(filePath, fileConfig, featureName, provider, p
319
311
  }
320
312
  // Handle file path adjustment based on project structure
321
313
  let targetFilePath = path.join(projectPath, filePath);
322
- // For Next.js projects, adjust file paths based on src folder structure
323
- if (projectInfo.framework === 'nextjs') {
324
- targetFilePath = adjustNextjsFilePath(filePath, projectInfo.hasSrcFolder || false, projectPath);
314
+ // For Next.js projects with src folder structure, adjust file paths accordingly
315
+ if (projectInfo.framework === 'nextjs' && projectInfo.hasSrcFolder) {
316
+ targetFilePath = adjustNextjsSrcFilePath(filePath, projectInfo.hasSrcFolder, projectPath);
325
317
  }
326
318
  // Ensure all parent directories exist before processing
327
319
  await fs.ensureDir(path.dirname(targetFilePath));
@@ -383,96 +375,157 @@ async function handleFileCreation(sourceFilePath, targetFilePath, cachedContent)
383
375
  console.log(chalk.green(`✅ Created: ${path.relative(process.cwd(), targetFilePath)}`));
384
376
  }
385
377
  /**
386
- * Handle file overwrite (replace existing content)
378
+ * Handle file overwrite (replace existing content or create if doesn't exist)
387
379
  */
388
380
  async function handleFileOverwrite(sourceFilePath, targetFilePath, cachedContent) {
389
- if (cachedContent) {
390
- await fs.outputFile(targetFilePath, cachedContent);
381
+ // Ensure target directory exists
382
+ await fs.ensureDir(path.dirname(targetFilePath));
383
+ const fileExists = await fs.pathExists(targetFilePath);
384
+ try {
385
+ if (cachedContent) {
386
+ await fs.outputFile(targetFilePath, cachedContent);
387
+ }
388
+ else {
389
+ // Check if source template exists
390
+ if (await fs.pathExists(sourceFilePath)) {
391
+ await copyTemplateFile(sourceFilePath, targetFilePath);
392
+ }
393
+ else {
394
+ console.log(chalk.yellow(`⚠️ Template file not found, skipping: ${path.relative(process.cwd(), sourceFilePath)}`));
395
+ console.log(chalk.gray(` This might be due to running a globally installed CLI. Consider using 'npx' or installing locally.`));
396
+ return;
397
+ }
398
+ }
399
+ if (fileExists) {
400
+ console.log(chalk.green(`✅ Updated: ${path.relative(process.cwd(), targetFilePath)}`));
401
+ }
402
+ else {
403
+ console.log(chalk.green(`✅ Created: ${path.relative(process.cwd(), targetFilePath)}`));
404
+ }
391
405
  }
392
- else {
393
- await copyTemplateFile(sourceFilePath, targetFilePath);
406
+ catch (error) {
407
+ console.error(chalk.red(`❌ Failed to overwrite/create ${path.relative(process.cwd(), targetFilePath)}: ${error}`));
408
+ throw error;
394
409
  }
395
- console.log(chalk.green(`✅ Updated: ${path.relative(process.cwd(), targetFilePath)}`));
396
410
  }
397
411
  /**
398
- * Handle file append (add content to end of file)
412
+ * Handle file append (add content to end of file, create if doesn't exist)
399
413
  */
400
414
  async function handleFileAppend(sourceFilePath, targetFilePath, cachedContent) {
415
+ // Ensure target directory exists
416
+ await fs.ensureDir(path.dirname(targetFilePath));
417
+ const fileExists = await fs.pathExists(targetFilePath);
401
418
  let existingContent = '';
402
- if (await fs.pathExists(targetFilePath)) {
403
- existingContent = await fs.readFile(targetFilePath, 'utf-8');
404
- }
405
- let templateContent;
406
- if (cachedContent) {
407
- templateContent = cachedContent;
419
+ try {
420
+ if (fileExists) {
421
+ existingContent = await fs.readFile(targetFilePath, 'utf8');
422
+ }
423
+ let contentToAppend = '';
424
+ if (cachedContent) {
425
+ contentToAppend = cachedContent;
426
+ }
427
+ else {
428
+ // Check if source template exists
429
+ if (await fs.pathExists(sourceFilePath)) {
430
+ contentToAppend = await fs.readFile(sourceFilePath, 'utf8');
431
+ }
432
+ else {
433
+ console.log(chalk.yellow(`⚠️ Template file not found, skipping append: ${path.relative(process.cwd(), sourceFilePath)}`));
434
+ console.log(chalk.gray(` This might be due to running a globally installed CLI. Consider using 'npx' or installing locally.`));
435
+ return;
436
+ }
437
+ }
438
+ const newContent = existingContent + contentToAppend;
439
+ await fs.outputFile(targetFilePath, newContent);
440
+ if (fileExists) {
441
+ console.log(chalk.green(`✅ Appended to: ${path.relative(process.cwd(), targetFilePath)}`));
442
+ }
443
+ else {
444
+ console.log(chalk.green(`✅ Created with content: ${path.relative(process.cwd(), targetFilePath)}`));
445
+ }
408
446
  }
409
- else {
410
- templateContent = await fs.readFile(sourceFilePath, 'utf-8');
447
+ catch (error) {
448
+ console.error(chalk.red(`❌ Failed to append/create ${path.relative(process.cwd(), targetFilePath)}: ${error}`));
449
+ throw error;
411
450
  }
412
- const separator = existingContent && !existingContent.endsWith('\n') ? '\n\n' : '\n';
413
- const newContent = existingContent + separator + templateContent;
414
- // Ensure target directory exists
415
- await fs.ensureDir(path.dirname(targetFilePath));
416
- await fs.outputFile(targetFilePath, newContent);
417
- console.log(chalk.green(`✅ Appended to: ${path.relative(process.cwd(), targetFilePath)}`));
418
451
  }
419
452
  /**
420
- * Handle file prepend (add content to beginning of file)
453
+ * Handle file prepend (add content to beginning of file, create if doesn't exist)
421
454
  */
422
455
  async function handleFilePrepend(sourceFilePath, targetFilePath, cachedContent) {
456
+ // Ensure target directory exists
457
+ await fs.ensureDir(path.dirname(targetFilePath));
458
+ const fileExists = await fs.pathExists(targetFilePath);
423
459
  let existingContent = '';
424
- if (await fs.pathExists(targetFilePath)) {
425
- existingContent = await fs.readFile(targetFilePath, 'utf-8');
426
- }
427
- let templateContent;
428
- if (cachedContent) {
429
- templateContent = cachedContent;
460
+ try {
461
+ if (fileExists) {
462
+ existingContent = await fs.readFile(targetFilePath, 'utf-8');
463
+ }
464
+ let templateContent;
465
+ if (cachedContent) {
466
+ templateContent = cachedContent;
467
+ }
468
+ else {
469
+ // Check if source template exists
470
+ if (await fs.pathExists(sourceFilePath)) {
471
+ templateContent = await fs.readFile(sourceFilePath, 'utf-8');
472
+ }
473
+ else {
474
+ console.log(chalk.yellow(`⚠️ Template file not found, skipping prepend: ${path.relative(process.cwd(), sourceFilePath)}`));
475
+ console.log(chalk.gray(` This might be due to running a globally installed CLI. Consider using 'npx' or installing locally.`));
476
+ return;
477
+ }
478
+ }
479
+ const separator = templateContent.endsWith('\n') ? '' : '\n';
480
+ const newContent = templateContent + separator + existingContent;
481
+ await fs.outputFile(targetFilePath, newContent);
482
+ if (fileExists) {
483
+ console.log(chalk.green(`✅ Prepended to: ${path.relative(process.cwd(), targetFilePath)}`));
484
+ }
485
+ else {
486
+ console.log(chalk.green(`✅ Created with content: ${path.relative(process.cwd(), targetFilePath)}`));
487
+ }
430
488
  }
431
- else {
432
- templateContent = await fs.readFile(sourceFilePath, 'utf-8');
489
+ catch (error) {
490
+ console.error(chalk.red(`❌ Failed to prepend/create ${path.relative(process.cwd(), targetFilePath)}: ${error}`));
491
+ throw error;
433
492
  }
434
- const separator = templateContent.endsWith('\n') ? '' : '\n';
435
- const newContent = templateContent + separator + existingContent;
436
- // Ensure target directory exists
437
- await fs.ensureDir(path.dirname(targetFilePath));
438
- await fs.outputFile(targetFilePath, newContent);
439
- console.log(chalk.green(`✅ Prepended to: ${path.relative(process.cwd(), targetFilePath)}`));
440
493
  }
441
494
  /**
442
- * Copy template file to target location with Next.js content processing
495
+ * Copy template file to target location with framework-agnostic content processing
443
496
  */
444
497
  async function copyTemplateFile(sourceFilePath, targetFilePath) {
445
498
  if (!await fs.pathExists(sourceFilePath)) {
499
+ const relativePath = path.relative(process.cwd(), sourceFilePath);
500
+ console.error(chalk.red(`❌ Template file not found: ${relativePath}`));
501
+ console.error(chalk.yellow(`💡 This might be due to running a globally installed CLI. Consider using 'npx' or installing locally.`));
446
502
  throw new Error(`Template file not found: ${sourceFilePath}`);
447
503
  }
448
- // Ensure target directory exists
449
- await fs.ensureDir(path.dirname(targetFilePath));
450
- // For Next.js projects, we might need to adjust import paths in template files
451
- if (path.extname(sourceFilePath).match(/\.(js|jsx|ts|tsx)$/)) {
452
- const templateContent = await fs.readFile(sourceFilePath, 'utf-8');
453
- // Process content for Next.js src folder structure
454
- let processedContent = templateContent;
455
- // Adjust import paths if needed (this is basic - you might want to make it more sophisticated)
456
- if (targetFilePath.includes('/src/')) {
457
- processedContent = processedContent.replace(/from ['"]@\//g, 'from "@/');
458
- processedContent = processedContent.replace(/from ['"]\.\.\//g, 'from "../');
459
- }
460
- await fs.writeFile(targetFilePath, processedContent);
504
+ try {
505
+ // Ensure target directory exists
506
+ await fs.ensureDir(path.dirname(targetFilePath));
507
+ // For code files, we might need to adjust import paths based on project structure
508
+ if (path.extname(sourceFilePath).match(/\.(js|jsx|ts|tsx)$/)) {
509
+ const templateContent = await fs.readFile(sourceFilePath, 'utf-8');
510
+ // Process content based on project structure (framework-agnostic)
511
+ let processedContent = templateContent;
512
+ // Adjust import paths for src-based project structures
513
+ if (targetFilePath.includes('/src/')) {
514
+ processedContent = processedContent.replace(/from ['"]@\//g, 'from "@/');
515
+ processedContent = processedContent.replace(/from ['"]\.\.\//g, 'from "../');
516
+ }
517
+ await fs.writeFile(targetFilePath, processedContent);
518
+ }
519
+ else {
520
+ // For non-code files, just copy directly
521
+ await fs.copy(sourceFilePath, targetFilePath);
522
+ }
461
523
  }
462
- else {
463
- // For non-code files, just copy directly
464
- await fs.copy(sourceFilePath, targetFilePath);
524
+ catch (error) {
525
+ console.error(chalk.red(`❌ Failed to copy template file: ${error}`));
526
+ throw error;
465
527
  }
466
528
  }
467
- /**
468
- * Get the CLI installation root directory
469
- */
470
- export function getCliRootPath() {
471
- const __filename = fileURLToPath(import.meta.url);
472
- const __dirname = path.dirname(__filename);
473
- // Go up from src/utils to root directory
474
- return path.resolve(__dirname, '..', '..');
475
- }
476
529
  /**
477
530
  * Show setup instructions for a feature
478
531
  */