frontend-hamroun 1.2.0 → 1.2.2
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/bin/cli.js +16 -0
- package/dist/cli/index.d.ts +2 -0
- package/dist/cli/index.js +215 -1
- package/package.json +10 -6
- package/scripts/build-cli.js +205 -0
- package/src/cli/index.ts +257 -0
- package/templates/full-stack/index.html +13 -0
- package/templates/full-stack/package.json +25 -18
- package/templates/full-stack/src/main.css +3 -0
- package/templates/full-stack/src/main.ts +20 -0
package/bin/cli.js
ADDED
@@ -0,0 +1,16 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
const path = require('path');
|
4
|
+
const fs = require('fs');
|
5
|
+
|
6
|
+
try {
|
7
|
+
// Try to load the ESM version
|
8
|
+
import('../dist/cli/index.js')
|
9
|
+
.catch(err => {
|
10
|
+
console.error('Failed to load CLI:', err);
|
11
|
+
process.exit(1);
|
12
|
+
});
|
13
|
+
} catch (error) {
|
14
|
+
console.error('Error loading CLI:', error);
|
15
|
+
process.exit(1);
|
16
|
+
}
|
package/dist/cli/index.d.ts
CHANGED
package/dist/cli/index.js
CHANGED
@@ -1 +1,215 @@
|
|
1
|
-
|
1
|
+
#!/usr/bin/env node
|
2
|
+
import fs from 'fs';
|
3
|
+
import path from 'path';
|
4
|
+
import { fileURLToPath } from 'url';
|
5
|
+
import { execSync } from 'child_process';
|
6
|
+
import readline from 'readline';
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
8
|
+
const __dirname = path.dirname(__filename);
|
9
|
+
// Get package.json to read version
|
10
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
11
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
12
|
+
// Available templates
|
13
|
+
const TEMPLATES = {
|
14
|
+
'basic': 'Basic frontend only template',
|
15
|
+
'full-stack': 'Complete frontend and backend template',
|
16
|
+
'api-only': 'Backend API only template'
|
17
|
+
};
|
18
|
+
// CLI colors
|
19
|
+
const colors = {
|
20
|
+
reset: '\x1b[0m',
|
21
|
+
bright: '\x1b[1m',
|
22
|
+
green: '\x1b[32m',
|
23
|
+
blue: '\x1b[34m',
|
24
|
+
red: '\x1b[31m',
|
25
|
+
yellow: '\x1b[33m'
|
26
|
+
};
|
27
|
+
// Create readline interface
|
28
|
+
function createInterface() {
|
29
|
+
return readline.createInterface({
|
30
|
+
input: process.stdin,
|
31
|
+
output: process.stdout
|
32
|
+
});
|
33
|
+
}
|
34
|
+
// Print banner
|
35
|
+
function printBanner() {
|
36
|
+
console.log(`
|
37
|
+
${colors.blue}${colors.bright}╔══════════════════════════════════════════════╗
|
38
|
+
║ ║
|
39
|
+
║ Frontend Hamroun v${packageJson.version.padEnd(25)}║
|
40
|
+
║ A lightweight frontend & backend framework ║
|
41
|
+
║ ║
|
42
|
+
╚══════════════════════════════════════════════╝${colors.reset}
|
43
|
+
`);
|
44
|
+
}
|
45
|
+
// Print help
|
46
|
+
function printHelp() {
|
47
|
+
console.log(`
|
48
|
+
${colors.bright}USAGE:${colors.reset}
|
49
|
+
${colors.blue}npx frontend-hamroun${colors.reset} [command] [options]
|
50
|
+
|
51
|
+
${colors.bright}COMMANDS:${colors.reset}
|
52
|
+
${colors.blue}create${colors.reset} <project-name> [options] Create a new project
|
53
|
+
${colors.blue}help${colors.reset} Display this help message
|
54
|
+
${colors.blue}version${colors.reset} Display version information
|
55
|
+
|
56
|
+
${colors.bright}OPTIONS:${colors.reset}
|
57
|
+
${colors.blue}--template${colors.reset}, ${colors.blue}-t${colors.reset} <template> Specify template (default: basic)
|
58
|
+
${colors.blue}--skip-install${colors.reset}, ${colors.blue}-s${colors.reset} Skip package installation
|
59
|
+
|
60
|
+
${colors.bright}AVAILABLE TEMPLATES:${colors.reset}
|
61
|
+
${Object.entries(TEMPLATES).map(([name, desc]) => ` ${colors.blue}${name.padEnd(12)}${colors.reset} ${desc}`).join('\n')}
|
62
|
+
|
63
|
+
${colors.bright}EXAMPLES:${colors.reset}
|
64
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-app
|
65
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-app --template full-stack
|
66
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-api -t api-only --skip-install
|
67
|
+
`);
|
68
|
+
}
|
69
|
+
// Create project from template
|
70
|
+
async function createProject(projectName, options) {
|
71
|
+
const template = options.template || 'basic';
|
72
|
+
// Check if template exists
|
73
|
+
if (!Object.keys(TEMPLATES).includes(template)) {
|
74
|
+
console.error(`${colors.red}error:${colors.reset} Template "${template}" not found.`);
|
75
|
+
console.log(`Available templates: ${Object.keys(TEMPLATES).join(', ')}`);
|
76
|
+
process.exit(1);
|
77
|
+
}
|
78
|
+
// Check if project directory already exists
|
79
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
80
|
+
if (fs.existsSync(projectPath)) {
|
81
|
+
console.error(`${colors.red}error:${colors.reset} Directory ${projectName} already exists.`);
|
82
|
+
process.exit(1);
|
83
|
+
}
|
84
|
+
console.log(`
|
85
|
+
${colors.bright}Creating a new project with Frontend Hamroun...${colors.reset}
|
86
|
+
${colors.blue}• Project name:${colors.reset} ${projectName}
|
87
|
+
${colors.blue}• Template:${colors.reset} ${template}
|
88
|
+
${colors.blue}• Directory:${colors.reset} ${projectPath}
|
89
|
+
`);
|
90
|
+
try {
|
91
|
+
// Find templates directory
|
92
|
+
const templateDir = path.resolve(__dirname, '../../templates', template);
|
93
|
+
// Create project directory
|
94
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
95
|
+
// Copy template files
|
96
|
+
copyTemplateFiles(templateDir, projectPath);
|
97
|
+
// Update package.json with project name
|
98
|
+
const projectPackageJsonPath = path.join(projectPath, 'package.json');
|
99
|
+
if (fs.existsSync(projectPackageJsonPath)) {
|
100
|
+
const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, 'utf8'));
|
101
|
+
projectPackageJson.name = projectName;
|
102
|
+
fs.writeFileSync(projectPackageJsonPath, JSON.stringify(projectPackageJson, null, 2));
|
103
|
+
}
|
104
|
+
// Install dependencies
|
105
|
+
if (!options.skipInstall) {
|
106
|
+
console.log(`\n${colors.blue}Installing dependencies...${colors.reset}`);
|
107
|
+
const command = getPackageManager() === 'yarn'
|
108
|
+
? 'yarn'
|
109
|
+
: 'npm install';
|
110
|
+
execSync(command, {
|
111
|
+
cwd: projectPath,
|
112
|
+
stdio: 'inherit'
|
113
|
+
});
|
114
|
+
}
|
115
|
+
// Success message
|
116
|
+
console.log(`
|
117
|
+
${colors.green}${colors.bright}Success!${colors.reset} Created ${projectName} at ${projectPath}
|
118
|
+
|
119
|
+
${colors.blue}Inside that directory, you can run several commands:${colors.reset}
|
120
|
+
|
121
|
+
${colors.bright}npm run dev${colors.reset}
|
122
|
+
Starts the development server.
|
123
|
+
|
124
|
+
${colors.bright}npm run build${colors.reset}
|
125
|
+
Builds the app for production.
|
126
|
+
|
127
|
+
${colors.bright}npm start${colors.reset}
|
128
|
+
Runs the built app in production mode.
|
129
|
+
|
130
|
+
${colors.blue}We suggest that you begin by typing:${colors.reset}
|
131
|
+
|
132
|
+
${colors.bright}cd${colors.reset} ${projectName}
|
133
|
+
${colors.bright}npm run dev${colors.reset}
|
134
|
+
|
135
|
+
${colors.green}Happy coding!${colors.reset}
|
136
|
+
`);
|
137
|
+
}
|
138
|
+
catch (error) {
|
139
|
+
console.error(`${colors.red}Failed to create project:${colors.reset}`, error);
|
140
|
+
process.exit(1);
|
141
|
+
}
|
142
|
+
}
|
143
|
+
// Copy template files recursively
|
144
|
+
function copyTemplateFiles(source, destination) {
|
145
|
+
const files = fs.readdirSync(source);
|
146
|
+
for (const file of files) {
|
147
|
+
const sourcePath = path.join(source, file);
|
148
|
+
const destPath = path.join(destination, file);
|
149
|
+
const stats = fs.statSync(sourcePath);
|
150
|
+
if (stats.isDirectory()) {
|
151
|
+
fs.mkdirSync(destPath, { recursive: true });
|
152
|
+
copyTemplateFiles(sourcePath, destPath);
|
153
|
+
}
|
154
|
+
else {
|
155
|
+
fs.copyFileSync(sourcePath, destPath);
|
156
|
+
}
|
157
|
+
}
|
158
|
+
console.log(`${colors.green}•${colors.reset} Copied template files`);
|
159
|
+
}
|
160
|
+
// Detect package manager
|
161
|
+
function getPackageManager() {
|
162
|
+
try {
|
163
|
+
const userAgent = process.env.npm_config_user_agent;
|
164
|
+
if (userAgent && userAgent.startsWith('yarn')) {
|
165
|
+
return 'yarn';
|
166
|
+
}
|
167
|
+
return 'npm';
|
168
|
+
}
|
169
|
+
catch (error) {
|
170
|
+
return 'npm';
|
171
|
+
}
|
172
|
+
}
|
173
|
+
// Parse command line arguments
|
174
|
+
function parseArgs() {
|
175
|
+
const args = process.argv.slice(2);
|
176
|
+
const command = args[0];
|
177
|
+
if (!command || command === 'help') {
|
178
|
+
printBanner();
|
179
|
+
printHelp();
|
180
|
+
process.exit(0);
|
181
|
+
}
|
182
|
+
if (command === 'version') {
|
183
|
+
console.log(`frontend-hamroun v${packageJson.version}`);
|
184
|
+
process.exit(0);
|
185
|
+
}
|
186
|
+
if (command === 'create') {
|
187
|
+
const projectName = args[1];
|
188
|
+
if (!projectName) {
|
189
|
+
console.error(`${colors.red}error:${colors.reset} Project name is required.`);
|
190
|
+
console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
|
191
|
+
process.exit(1);
|
192
|
+
}
|
193
|
+
// Parse options
|
194
|
+
const options = {
|
195
|
+
template: 'basic',
|
196
|
+
skipInstall: false
|
197
|
+
};
|
198
|
+
for (let i = 2; i < args.length; i++) {
|
199
|
+
if (args[i] === '--template' || args[i] === '-t') {
|
200
|
+
options.template = args[++i];
|
201
|
+
}
|
202
|
+
else if (args[i] === '--skip-install' || args[i] === '-s') {
|
203
|
+
options.skipInstall = true;
|
204
|
+
}
|
205
|
+
}
|
206
|
+
printBanner();
|
207
|
+
createProject(projectName, options);
|
208
|
+
return;
|
209
|
+
}
|
210
|
+
console.error(`${colors.red}error:${colors.reset} Unknown command: ${command}`);
|
211
|
+
console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
|
212
|
+
process.exit(1);
|
213
|
+
}
|
214
|
+
// Run CLI
|
215
|
+
parseArgs();
|
package/package.json
CHANGED
@@ -1,6 +1,6 @@
|
|
1
1
|
{
|
2
2
|
"name": "frontend-hamroun",
|
3
|
-
"version": "1.2.
|
3
|
+
"version": "1.2.2",
|
4
4
|
"description": "A lightweight frontend and backend framework for building modern web applications",
|
5
5
|
"type": "module",
|
6
6
|
"main": "dist/frontend-hamroun.umd.js",
|
@@ -11,6 +11,7 @@
|
|
11
11
|
"src",
|
12
12
|
"templates",
|
13
13
|
"scripts",
|
14
|
+
"bin",
|
14
15
|
"LICENSE",
|
15
16
|
"README.md"
|
16
17
|
],
|
@@ -22,16 +23,19 @@
|
|
22
23
|
}
|
23
24
|
},
|
24
25
|
"bin": {
|
25
|
-
"hamroun": "
|
26
|
+
"hamroun": "./bin/cli.js",
|
27
|
+
"frontend-hamroun": "./bin/cli.js"
|
26
28
|
},
|
27
29
|
"scripts": {
|
28
30
|
"dev": "vite",
|
29
|
-
"build": "vite build && tsc",
|
31
|
+
"build": "vite build && tsc && npm run build:cli",
|
32
|
+
"build:cli": "node scripts/build-cli.js",
|
33
|
+
"prepublishOnly": "npm run build",
|
30
34
|
"test": "jest",
|
31
|
-
"clean": "rimraf dist",
|
35
|
+
"clean": "rimraf dist bin",
|
32
36
|
"prebuild": "npm run clean",
|
33
|
-
"
|
34
|
-
"
|
37
|
+
"generate": "node scripts/generate.js",
|
38
|
+
"publish:next": "npm publish --tag next"
|
35
39
|
},
|
36
40
|
"keywords": [
|
37
41
|
"frontend",
|
@@ -0,0 +1,205 @@
|
|
1
|
+
import fs from 'fs';
|
2
|
+
import path from 'path';
|
3
|
+
import { fileURLToPath } from 'url';
|
4
|
+
import { execSync } from 'child_process';
|
5
|
+
|
6
|
+
const __filename = fileURLToPath(import.meta.url);
|
7
|
+
const __dirname = path.dirname(__filename);
|
8
|
+
const rootDir = path.resolve(__dirname, '..');
|
9
|
+
const binDir = path.resolve(rootDir, 'bin');
|
10
|
+
|
11
|
+
// Ensure bin directory exists
|
12
|
+
if (!fs.existsSync(binDir)) {
|
13
|
+
fs.mkdirSync(binDir, { recursive: true });
|
14
|
+
}
|
15
|
+
|
16
|
+
// Create a simple CLI wrapper that doesn't rely on TypeScript
|
17
|
+
const cliContent = `#!/usr/bin/env node
|
18
|
+
|
19
|
+
const path = require('path');
|
20
|
+
const fs = require('fs');
|
21
|
+
|
22
|
+
try {
|
23
|
+
// Try to load the ESM version
|
24
|
+
import('../dist/cli/index.js')
|
25
|
+
.catch(err => {
|
26
|
+
console.error('Failed to load CLI:', err);
|
27
|
+
process.exit(1);
|
28
|
+
});
|
29
|
+
} catch (error) {
|
30
|
+
console.error('Error loading CLI:', error);
|
31
|
+
process.exit(1);
|
32
|
+
}
|
33
|
+
`;
|
34
|
+
|
35
|
+
// Write CLI file
|
36
|
+
fs.writeFileSync(path.join(binDir, 'cli.js'), cliContent, 'utf8');
|
37
|
+
|
38
|
+
// Make CLI executable
|
39
|
+
try {
|
40
|
+
fs.chmodSync(path.join(binDir, 'cli.js'), '755');
|
41
|
+
console.log('✅ CLI wrapper built successfully: bin/cli.js');
|
42
|
+
} catch (error) {
|
43
|
+
console.warn('⚠️ Could not make CLI executable:', error);
|
44
|
+
}
|
45
|
+
|
46
|
+
// Copy the CLI code to dist directory and compile it
|
47
|
+
console.log('🔨 Compiling CLI TypeScript...');
|
48
|
+
try {
|
49
|
+
// Ensure dist/cli directory exists
|
50
|
+
const distCliDir = path.resolve(rootDir, 'dist/cli');
|
51
|
+
if (!fs.existsSync(distCliDir)) {
|
52
|
+
fs.mkdirSync(distCliDir, { recursive: true });
|
53
|
+
}
|
54
|
+
|
55
|
+
// Extract TypeScript files needed for CLI
|
56
|
+
const tscCommand = `npx tsc src/cli/index.ts --outDir dist/cli --esModuleInterop --target ES2020 --module NodeNext --moduleResolution NodeNext`;
|
57
|
+
|
58
|
+
// Execute TypeScript compilation
|
59
|
+
execSync(tscCommand, { stdio: 'inherit' });
|
60
|
+
|
61
|
+
console.log('✅ CLI TypeScript compiled successfully');
|
62
|
+
} catch (error) {
|
63
|
+
console.error('❌ Failed to compile CLI TypeScript:', error);
|
64
|
+
|
65
|
+
// Create a fallback CLI if TypeScript compilation fails
|
66
|
+
console.log('⚠️ Creating fallback CommonJS CLI...');
|
67
|
+
|
68
|
+
const fallbackCli = `
|
69
|
+
// This is a fallback CLI implementation when TypeScript compilation fails
|
70
|
+
const fs = require('fs');
|
71
|
+
const path = require('path');
|
72
|
+
const { execSync } = require('child_process');
|
73
|
+
|
74
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
75
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
76
|
+
|
77
|
+
// Available templates
|
78
|
+
const TEMPLATES = {
|
79
|
+
'basic': 'Basic frontend only template',
|
80
|
+
'full-stack': 'Complete frontend and backend template'
|
81
|
+
};
|
82
|
+
|
83
|
+
// Colors for console output
|
84
|
+
const colors = {
|
85
|
+
reset: '\\x1b[0m',
|
86
|
+
bright: '\\x1b[1m',
|
87
|
+
green: '\\x1b[32m',
|
88
|
+
blue: '\\x1b[34m',
|
89
|
+
red: '\\x1b[31m'
|
90
|
+
};
|
91
|
+
|
92
|
+
console.log(\`
|
93
|
+
\${colors.blue}\${colors.bright}╔══════════════════════════════════════════════╗
|
94
|
+
║ ║
|
95
|
+
║ Frontend Hamroun v\${packageJson.version.padEnd(25)}║
|
96
|
+
║ A lightweight frontend & backend framework ║
|
97
|
+
║ ║
|
98
|
+
╚══════════════════════════════════════════════╝\${colors.reset}
|
99
|
+
\`);
|
100
|
+
|
101
|
+
const args = process.argv.slice(2);
|
102
|
+
const command = args[0];
|
103
|
+
|
104
|
+
if (!command || command === 'help') {
|
105
|
+
console.log(\`
|
106
|
+
\${colors.bright}USAGE:\${colors.reset}
|
107
|
+
\${colors.blue}npx frontend-hamroun\${colors.reset} create <project-name> [--template <template>]
|
108
|
+
|
109
|
+
\${colors.bright}AVAILABLE TEMPLATES:\${colors.reset}
|
110
|
+
\${colors.blue}basic\${colors.reset} Basic frontend only template
|
111
|
+
\${colors.blue}full-stack\${colors.reset} Complete frontend and backend template
|
112
|
+
\`);
|
113
|
+
process.exit(0);
|
114
|
+
}
|
115
|
+
|
116
|
+
if (command === 'create') {
|
117
|
+
const projectName = args[1];
|
118
|
+
|
119
|
+
if (!projectName) {
|
120
|
+
console.error(\`\${colors.red}Error:\${colors.reset} Project name is required\`);
|
121
|
+
process.exit(1);
|
122
|
+
}
|
123
|
+
|
124
|
+
let template = 'basic';
|
125
|
+
for (let i = 2; i < args.length; i++) {
|
126
|
+
if (args[i] === '--template' || args[i] === '-t') {
|
127
|
+
template = args[++i] || 'basic';
|
128
|
+
}
|
129
|
+
}
|
130
|
+
|
131
|
+
if (!Object.keys(TEMPLATES).includes(template)) {
|
132
|
+
console.error(\`\${colors.red}Error:\${colors.reset} Template "\${template}" not found\`);
|
133
|
+
console.log(\`Available templates: \${Object.keys(TEMPLATES).join(', ')}\`);
|
134
|
+
process.exit(1);
|
135
|
+
}
|
136
|
+
|
137
|
+
console.log(\`Creating new \${template} project: \${projectName}...\`);
|
138
|
+
|
139
|
+
try {
|
140
|
+
// Create project directory
|
141
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
142
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
143
|
+
|
144
|
+
// Create minimal package.json
|
145
|
+
const pkgJson = {
|
146
|
+
"name": projectName,
|
147
|
+
"version": "0.1.0",
|
148
|
+
"private": true,
|
149
|
+
"dependencies": {
|
150
|
+
"frontend-hamroun": packageJson.version
|
151
|
+
},
|
152
|
+
"scripts": {
|
153
|
+
"dev": "vite",
|
154
|
+
"build": "vite build"
|
155
|
+
}
|
156
|
+
};
|
157
|
+
|
158
|
+
fs.writeFileSync(
|
159
|
+
path.join(projectPath, 'package.json'),
|
160
|
+
JSON.stringify(pkgJson, null, 2)
|
161
|
+
);
|
162
|
+
|
163
|
+
console.log(\`
|
164
|
+
\${colors.green}\${colors.bright}Success!\${colors.reset} Created project at \${projectPath}
|
165
|
+
|
166
|
+
\${colors.blue}Next steps:\${colors.reset}
|
167
|
+
cd \${projectName}
|
168
|
+
npm install
|
169
|
+
npm run dev
|
170
|
+
\`);
|
171
|
+
|
172
|
+
} catch (error) {
|
173
|
+
console.error(\`\${colors.red}Failed to create project:\${colors.reset}\`, error);
|
174
|
+
process.exit(1);
|
175
|
+
}
|
176
|
+
} else {
|
177
|
+
console.error(\`\${colors.red}Unknown command:\${colors.reset} \${command}\`);
|
178
|
+
process.exit(1);
|
179
|
+
}
|
180
|
+
`;
|
181
|
+
|
182
|
+
const distCliDir = path.resolve(rootDir, 'dist/cli');
|
183
|
+
if (!fs.existsSync(distCliDir)) {
|
184
|
+
fs.mkdirSync(distCliDir, { recursive: true });
|
185
|
+
}
|
186
|
+
|
187
|
+
fs.writeFileSync(path.join(distCliDir, 'index.js'), fallbackCli, 'utf8');
|
188
|
+
console.log('✅ Fallback CLI created successfully');
|
189
|
+
}
|
190
|
+
|
191
|
+
// Let's make sure we also have template directories
|
192
|
+
const templatesDir = path.resolve(rootDir, 'templates');
|
193
|
+
const templateList = ['basic', 'full-stack'];
|
194
|
+
|
195
|
+
console.log('🔍 Checking template directories...');
|
196
|
+
for (const template of templateList) {
|
197
|
+
const templateDir = path.join(templatesDir, template);
|
198
|
+
if (!fs.existsSync(templateDir)) {
|
199
|
+
console.log(`⚠️ Template directory not found: ${templateDir}`);
|
200
|
+
} else {
|
201
|
+
console.log(`✅ Template found: ${template}`);
|
202
|
+
}
|
203
|
+
}
|
204
|
+
|
205
|
+
console.log('✅ CLI build process completed');
|
package/src/cli/index.ts
CHANGED
@@ -0,0 +1,257 @@
|
|
1
|
+
#!/usr/bin/env node
|
2
|
+
|
3
|
+
import fs from 'fs';
|
4
|
+
import path from 'path';
|
5
|
+
import { fileURLToPath } from 'url';
|
6
|
+
import { execSync } from 'child_process';
|
7
|
+
import readline from 'readline';
|
8
|
+
|
9
|
+
const __filename = fileURLToPath(import.meta.url);
|
10
|
+
const __dirname = path.dirname(__filename);
|
11
|
+
|
12
|
+
// Get package.json to read version
|
13
|
+
const packageJsonPath = path.resolve(__dirname, '../../package.json');
|
14
|
+
const packageJson = JSON.parse(fs.readFileSync(packageJsonPath, 'utf8'));
|
15
|
+
|
16
|
+
// Define interfaces for type safety
|
17
|
+
interface ProjectOptions {
|
18
|
+
template: string;
|
19
|
+
skipInstall: boolean;
|
20
|
+
}
|
21
|
+
|
22
|
+
// Available templates
|
23
|
+
const TEMPLATES: Record<string, string> = {
|
24
|
+
'basic': 'Basic frontend only template',
|
25
|
+
'full-stack': 'Complete frontend and backend template',
|
26
|
+
'api-only': 'Backend API only template'
|
27
|
+
};
|
28
|
+
|
29
|
+
// CLI colors
|
30
|
+
const colors = {
|
31
|
+
reset: '\x1b[0m',
|
32
|
+
bright: '\x1b[1m',
|
33
|
+
green: '\x1b[32m',
|
34
|
+
blue: '\x1b[34m',
|
35
|
+
red: '\x1b[31m',
|
36
|
+
yellow: '\x1b[33m'
|
37
|
+
};
|
38
|
+
|
39
|
+
// Create readline interface
|
40
|
+
function createInterface(): readline.Interface {
|
41
|
+
return readline.createInterface({
|
42
|
+
input: process.stdin,
|
43
|
+
output: process.stdout
|
44
|
+
});
|
45
|
+
}
|
46
|
+
|
47
|
+
// Print banner
|
48
|
+
function printBanner(): void {
|
49
|
+
console.log(`
|
50
|
+
${colors.blue}${colors.bright}╔══════════════════════════════════════════════╗
|
51
|
+
║ ║
|
52
|
+
║ Frontend Hamroun v${packageJson.version.padEnd(25)}║
|
53
|
+
║ A lightweight frontend & backend framework ║
|
54
|
+
║ ║
|
55
|
+
╚══════════════════════════════════════════════╝${colors.reset}
|
56
|
+
`);
|
57
|
+
}
|
58
|
+
|
59
|
+
// Print help
|
60
|
+
function printHelp(): void {
|
61
|
+
console.log(`
|
62
|
+
${colors.bright}USAGE:${colors.reset}
|
63
|
+
${colors.blue}npx frontend-hamroun${colors.reset} [command] [options]
|
64
|
+
|
65
|
+
${colors.bright}COMMANDS:${colors.reset}
|
66
|
+
${colors.blue}create${colors.reset} <project-name> [options] Create a new project
|
67
|
+
${colors.blue}help${colors.reset} Display this help message
|
68
|
+
${colors.blue}version${colors.reset} Display version information
|
69
|
+
|
70
|
+
${colors.bright}OPTIONS:${colors.reset}
|
71
|
+
${colors.blue}--template${colors.reset}, ${colors.blue}-t${colors.reset} <template> Specify template (default: basic)
|
72
|
+
${colors.blue}--skip-install${colors.reset}, ${colors.blue}-s${colors.reset} Skip package installation
|
73
|
+
|
74
|
+
${colors.bright}AVAILABLE TEMPLATES:${colors.reset}
|
75
|
+
${Object.entries(TEMPLATES).map(([name, desc]) => ` ${colors.blue}${name.padEnd(12)}${colors.reset} ${desc}`).join('\n')}
|
76
|
+
|
77
|
+
${colors.bright}EXAMPLES:${colors.reset}
|
78
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-app
|
79
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-app --template full-stack
|
80
|
+
${colors.blue}npx frontend-hamroun create${colors.reset} my-api -t api-only --skip-install
|
81
|
+
`);
|
82
|
+
}
|
83
|
+
|
84
|
+
// Create project from template
|
85
|
+
async function createProject(projectName: string, options: ProjectOptions): Promise<void> {
|
86
|
+
const template = options.template || 'basic';
|
87
|
+
|
88
|
+
// Check if template exists
|
89
|
+
if (!Object.keys(TEMPLATES).includes(template)) {
|
90
|
+
console.error(`${colors.red}error:${colors.reset} Template "${template}" not found.`);
|
91
|
+
console.log(`Available templates: ${Object.keys(TEMPLATES).join(', ')}`);
|
92
|
+
process.exit(1);
|
93
|
+
}
|
94
|
+
|
95
|
+
// Check if project directory already exists
|
96
|
+
const projectPath = path.resolve(process.cwd(), projectName);
|
97
|
+
if (fs.existsSync(projectPath)) {
|
98
|
+
console.error(`${colors.red}error:${colors.reset} Directory ${projectName} already exists.`);
|
99
|
+
process.exit(1);
|
100
|
+
}
|
101
|
+
|
102
|
+
console.log(`
|
103
|
+
${colors.bright}Creating a new project with Frontend Hamroun...${colors.reset}
|
104
|
+
${colors.blue}• Project name:${colors.reset} ${projectName}
|
105
|
+
${colors.blue}• Template:${colors.reset} ${template}
|
106
|
+
${colors.blue}• Directory:${colors.reset} ${projectPath}
|
107
|
+
`);
|
108
|
+
|
109
|
+
try {
|
110
|
+
// Find templates directory
|
111
|
+
const templateDir = path.resolve(__dirname, '../../templates', template);
|
112
|
+
|
113
|
+
// Create project directory
|
114
|
+
fs.mkdirSync(projectPath, { recursive: true });
|
115
|
+
|
116
|
+
// Copy template files
|
117
|
+
copyTemplateFiles(templateDir, projectPath);
|
118
|
+
|
119
|
+
// Update package.json with project name
|
120
|
+
const projectPackageJsonPath = path.join(projectPath, 'package.json');
|
121
|
+
if (fs.existsSync(projectPackageJsonPath)) {
|
122
|
+
const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, 'utf8'));
|
123
|
+
projectPackageJson.name = projectName;
|
124
|
+
fs.writeFileSync(
|
125
|
+
projectPackageJsonPath,
|
126
|
+
JSON.stringify(projectPackageJson, null, 2)
|
127
|
+
);
|
128
|
+
}
|
129
|
+
|
130
|
+
// Install dependencies
|
131
|
+
if (!options.skipInstall) {
|
132
|
+
console.log(`\n${colors.blue}Installing dependencies...${colors.reset}`);
|
133
|
+
|
134
|
+
const command = getPackageManager() === 'yarn'
|
135
|
+
? 'yarn'
|
136
|
+
: 'npm install';
|
137
|
+
|
138
|
+
execSync(command, {
|
139
|
+
cwd: projectPath,
|
140
|
+
stdio: 'inherit'
|
141
|
+
});
|
142
|
+
}
|
143
|
+
|
144
|
+
// Success message
|
145
|
+
console.log(`
|
146
|
+
${colors.green}${colors.bright}Success!${colors.reset} Created ${projectName} at ${projectPath}
|
147
|
+
|
148
|
+
${colors.blue}Inside that directory, you can run several commands:${colors.reset}
|
149
|
+
|
150
|
+
${colors.bright}npm run dev${colors.reset}
|
151
|
+
Starts the development server.
|
152
|
+
|
153
|
+
${colors.bright}npm run build${colors.reset}
|
154
|
+
Builds the app for production.
|
155
|
+
|
156
|
+
${colors.bright}npm start${colors.reset}
|
157
|
+
Runs the built app in production mode.
|
158
|
+
|
159
|
+
${colors.blue}We suggest that you begin by typing:${colors.reset}
|
160
|
+
|
161
|
+
${colors.bright}cd${colors.reset} ${projectName}
|
162
|
+
${colors.bright}npm run dev${colors.reset}
|
163
|
+
|
164
|
+
${colors.green}Happy coding!${colors.reset}
|
165
|
+
`);
|
166
|
+
|
167
|
+
} catch (error) {
|
168
|
+
console.error(`${colors.red}Failed to create project:${colors.reset}`, error);
|
169
|
+
process.exit(1);
|
170
|
+
}
|
171
|
+
}
|
172
|
+
|
173
|
+
// Copy template files recursively
|
174
|
+
function copyTemplateFiles(source: string, destination: string): void {
|
175
|
+
const files = fs.readdirSync(source);
|
176
|
+
|
177
|
+
for (const file of files) {
|
178
|
+
const sourcePath = path.join(source, file);
|
179
|
+
const destPath = path.join(destination, file);
|
180
|
+
|
181
|
+
const stats = fs.statSync(sourcePath);
|
182
|
+
|
183
|
+
if (stats.isDirectory()) {
|
184
|
+
fs.mkdirSync(destPath, { recursive: true });
|
185
|
+
copyTemplateFiles(sourcePath, destPath);
|
186
|
+
} else {
|
187
|
+
fs.copyFileSync(sourcePath, destPath);
|
188
|
+
}
|
189
|
+
}
|
190
|
+
|
191
|
+
console.log(`${colors.green}•${colors.reset} Copied template files`);
|
192
|
+
}
|
193
|
+
|
194
|
+
// Detect package manager
|
195
|
+
function getPackageManager(): string {
|
196
|
+
try {
|
197
|
+
const userAgent = process.env.npm_config_user_agent;
|
198
|
+
if (userAgent && userAgent.startsWith('yarn')) {
|
199
|
+
return 'yarn';
|
200
|
+
}
|
201
|
+
return 'npm';
|
202
|
+
} catch (error) {
|
203
|
+
return 'npm';
|
204
|
+
}
|
205
|
+
}
|
206
|
+
|
207
|
+
// Parse command line arguments
|
208
|
+
function parseArgs(): void {
|
209
|
+
const args = process.argv.slice(2);
|
210
|
+
const command = args[0];
|
211
|
+
|
212
|
+
if (!command || command === 'help') {
|
213
|
+
printBanner();
|
214
|
+
printHelp();
|
215
|
+
process.exit(0);
|
216
|
+
}
|
217
|
+
|
218
|
+
if (command === 'version') {
|
219
|
+
console.log(`frontend-hamroun v${packageJson.version}`);
|
220
|
+
process.exit(0);
|
221
|
+
}
|
222
|
+
|
223
|
+
if (command === 'create') {
|
224
|
+
const projectName = args[1];
|
225
|
+
|
226
|
+
if (!projectName) {
|
227
|
+
console.error(`${colors.red}error:${colors.reset} Project name is required.`);
|
228
|
+
console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
|
229
|
+
process.exit(1);
|
230
|
+
}
|
231
|
+
|
232
|
+
// Parse options
|
233
|
+
const options: ProjectOptions = {
|
234
|
+
template: 'basic',
|
235
|
+
skipInstall: false
|
236
|
+
};
|
237
|
+
|
238
|
+
for (let i = 2; i < args.length; i++) {
|
239
|
+
if (args[i] === '--template' || args[i] === '-t') {
|
240
|
+
options.template = args[++i];
|
241
|
+
} else if (args[i] === '--skip-install' || args[i] === '-s') {
|
242
|
+
options.skipInstall = true;
|
243
|
+
}
|
244
|
+
}
|
245
|
+
|
246
|
+
printBanner();
|
247
|
+
createProject(projectName, options);
|
248
|
+
return;
|
249
|
+
}
|
250
|
+
|
251
|
+
console.error(`${colors.red}error:${colors.reset} Unknown command: ${command}`);
|
252
|
+
console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
|
253
|
+
process.exit(1);
|
254
|
+
}
|
255
|
+
|
256
|
+
// Run CLI
|
257
|
+
parseArgs();
|
@@ -0,0 +1,13 @@
|
|
1
|
+
<!DOCTYPE html>
|
2
|
+
<html lang="en">
|
3
|
+
<head>
|
4
|
+
<meta charset="UTF-8">
|
5
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
6
|
+
<title>Frontend Hamroun App</title>
|
7
|
+
<link rel="stylesheet" href="/src/main.css">
|
8
|
+
</head>
|
9
|
+
<body>
|
10
|
+
<div id="app"></div>
|
11
|
+
<script type="module" src="/src/main.ts"></script>
|
12
|
+
</body>
|
13
|
+
</html>
|
@@ -1,43 +1,50 @@
|
|
1
1
|
{
|
2
|
-
"name": "
|
2
|
+
"name": "frontend-hamroun-app",
|
3
|
+
"private": true,
|
3
4
|
"version": "0.1.0",
|
4
5
|
"description": "{{description}}",
|
5
6
|
"author": "{{author}}",
|
6
7
|
"license": "MIT",
|
7
8
|
"type": "module",
|
8
9
|
"scripts": {
|
9
|
-
"dev": "
|
10
|
-
"
|
11
|
-
"
|
12
|
-
"
|
13
|
-
"
|
14
|
-
"
|
10
|
+
"dev": "vite",
|
11
|
+
"build": "tsc && vite build",
|
12
|
+
"preview": "vite preview",
|
13
|
+
"start": "node dist/server.js",
|
14
|
+
"dev:server": "ts-node-esm --transpile-only src/server.ts",
|
15
|
+
"dev:full": "concurrently \"npm run dev\" \"npm run dev:server\""
|
15
16
|
},
|
16
17
|
"dependencies": {
|
18
|
+
"frontend-hamroun": "latest",
|
17
19
|
"express": "^4.18.2",
|
18
|
-
"
|
20
|
+
"compression": "^1.7.4",
|
21
|
+
"helmet": "^7.0.0",
|
22
|
+
"morgan": "^1.10.0",
|
19
23
|
"mongoose": "^7.0.0",
|
20
24
|
"bcrypt": "^5.1.0",
|
21
25
|
"jsonwebtoken": "^9.0.0",
|
22
|
-
"compression": "^1.7.4",
|
23
26
|
"cookie-parser": "^1.4.6",
|
24
|
-
"dotenv": "^16.0.3"
|
25
|
-
"helmet": "^6.0.1"
|
27
|
+
"dotenv": "^16.0.3"
|
26
28
|
},
|
27
29
|
"devDependencies": {
|
30
|
+
"@tailwindcss/forms": "^0.5.7",
|
31
|
+
"@tailwindcss/typography": "^0.5.10",
|
32
|
+
"@types/express": "^4.17.21",
|
33
|
+
"@types/node": "^20.10.0",
|
34
|
+
"autoprefixer": "^10.4.16",
|
35
|
+
"concurrently": "^8.2.2",
|
36
|
+
"postcss": "^8.4.31",
|
37
|
+
"tailwindcss": "^3.3.5",
|
38
|
+
"ts-node": "^10.9.1",
|
39
|
+
"typescript": "^5.3.2",
|
40
|
+
"vite": "^5.0.0",
|
28
41
|
"@types/bcrypt": "^5.0.0",
|
29
42
|
"@types/compression": "^1.7.2",
|
30
43
|
"@types/cookie-parser": "^1.4.3",
|
31
|
-
"@types/express": "^4.17.17",
|
32
44
|
"@types/jest": "^29.5.0",
|
33
45
|
"@types/jsonwebtoken": "^9.0.1",
|
34
|
-
"@types/node": "^18.15.0",
|
35
|
-
"concurrently": "^7.6.0",
|
36
46
|
"jest": "^29.5.0",
|
37
47
|
"nodemon": "^2.0.21",
|
38
|
-
"ts-jest": "^29.0.5"
|
39
|
-
"ts-node": "^10.9.1",
|
40
|
-
"typescript": "^4.9.5",
|
41
|
-
"vite": "^4.1.4"
|
48
|
+
"ts-jest": "^29.0.5"
|
42
49
|
}
|
43
50
|
}
|
@@ -0,0 +1,20 @@
|
|
1
|
+
import './main.css';
|
2
|
+
import { render } from 'frontend-hamroun';
|
3
|
+
import { App } from './App';
|
4
|
+
import { createApiClient } from './api';
|
5
|
+
|
6
|
+
// Initialize API client
|
7
|
+
const api = createApiClient({
|
8
|
+
baseUrl: '/api'
|
9
|
+
});
|
10
|
+
|
11
|
+
// Render the app into the DOM
|
12
|
+
document.addEventListener('DOMContentLoaded', () => {
|
13
|
+
const rootElement = document.getElementById('app');
|
14
|
+
if (rootElement) {
|
15
|
+
render(<App api={api} />, rootElement);
|
16
|
+
console.log('App rendered successfully');
|
17
|
+
} else {
|
18
|
+
console.error('Root element #app not found');
|
19
|
+
}
|
20
|
+
});
|