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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +8 -0
- data/dist/commands/add.js +7 -49
- data/dist/index.js +17 -10
- data/dist/utils/banner.js +3 -1
- data/dist/utils/cacheUtils.js +1 -1
- data/dist/utils/dashboard.js +1 -1
- data/dist/utils/dependencyInstaller.js +2 -46
- data/dist/utils/featureInstaller.js +158 -105
- data/dist/utils/languageConfig.js +169 -300
- data/dist/utils/pathResolver.js +179 -0
- data/dist/utils/prompts.js +5 -14
- data/dist/utils/templateCreator.js +3 -3
- data/dist/utils/templateResolver.js +8 -17
- data/dist/utils/types.js +2 -2
- data/dist/utils/ui.js +2 -2
- data/dist/utils/utils.js +20 -1
- data/lib/package_installer_cli.rb +1 -1
- data/templates/django/django-full-stack-template/django-project-template/deployment/docker/build.sh +0 -0
- data/templates/django/django-full-stack-template/django-project-template/deployment/docker/manage.py +0 -0
- data/templates/django/django-full-stack-template/django-project-template/src/compile-locale.sh +0 -0
- data/templates/django/django-full-stack-template/django-project-template/src/compile-sass.sh +0 -0
- data/templates/django/django-full-stack-template/django-project-template/src/manage.py +0 -0
- data/templates/django/django-inertia-svelte-template/django-inertia-svelte-template-starter/manage.py +0 -0
- data/templates/flask/flask-cookiecutter-advance-template/cookiecutter-docker.sh +0 -0
- data/templates/flask/flask-cookiecutter-advance-template/{{cookiecutter.app_name}}/autoapp.py +0 -0
- data/templates/flask/flask-project-template/apply.sh +0 -0
- data/templates/react-native/typescript/template/android/gradlew +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/bundle +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/dev +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/docker-entrypoint +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/rails +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/rake +0 -0
- data/templates/ruby/rails_7_esbuild_hotwire_tailwindcss_starter/bin/setup +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/bundle +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/rails +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/rake +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/setup +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/spring +0 -0
- data/templates/ruby/ruby-on-rails-apis-template/bin/update +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/bundle +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/dev +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/importmap +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/rails +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/rake +0 -0
- data/templates/ruby/ruby-on-rails-boilerplate-template/bin/setup +0 -0
- metadata +6 -5
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 7f432378951e281eb37210295d7894abbf072093bd9a2e6099f1d3219ad45eb0
|
|
4
|
+
data.tar.gz: 0e9edf8310281ce583765615d8d41f1897b7e0fab8e6cc3dbbfa405d39a63cc9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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 {
|
|
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
|
-
//
|
|
63
|
-
const
|
|
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(
|
|
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
|
|
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 =
|
|
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
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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!'), {
|
data/dist/utils/cacheUtils.js
CHANGED
data/dist/utils/dashboard.js
CHANGED
|
@@ -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.
|
|
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.
|
|
3
|
-
* Supports Node.js,
|
|
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
|
|
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
|
|
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
|
|
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
|
|
80
|
-
//
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
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
|
|
109
|
-
|
|
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
|
-
|
|
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
|
|
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
|
|
323
|
-
if (projectInfo.framework === 'nextjs') {
|
|
324
|
-
targetFilePath =
|
|
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
|
-
|
|
390
|
-
|
|
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
|
-
|
|
393
|
-
|
|
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
|
-
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
|
|
407
|
-
|
|
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
|
-
|
|
410
|
-
|
|
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
|
-
|
|
425
|
-
|
|
426
|
-
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
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
|
-
|
|
432
|
-
|
|
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
|
|
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
|
-
|
|
449
|
-
|
|
450
|
-
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
|
|
454
|
-
|
|
455
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
460
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
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
|
*/
|