frontend-hamroun 1.1.90 → 1.2.1

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 (97) hide show
  1. package/dist/{src/backend → backend}/api-utils.d.ts +2 -2
  2. package/dist/backend/api-utils.js +135 -0
  3. package/dist/backend/auth.js +387 -0
  4. package/dist/{src/backend → backend}/database.d.ts +1 -1
  5. package/dist/backend/database.js +91 -0
  6. package/dist/{src/backend → backend}/model.d.ts +2 -2
  7. package/dist/backend/model.js +176 -0
  8. package/dist/{src/backend → backend}/router.d.ts +1 -1
  9. package/dist/backend/router.js +137 -0
  10. package/dist/backend/server.js +268 -0
  11. package/dist/batch.js +22 -0
  12. package/dist/cli/index.js +1 -0
  13. package/dist/{src/component.d.ts → component.d.ts} +1 -1
  14. package/dist/component.js +84 -0
  15. package/dist/components/Counter.js +2 -0
  16. package/dist/context.js +20 -0
  17. package/dist/frontend-hamroun.es.js +1680 -0
  18. package/dist/frontend-hamroun.es.js.map +1 -0
  19. package/dist/frontend-hamroun.umd.js +2 -0
  20. package/dist/frontend-hamroun.umd.js.map +1 -0
  21. package/dist/hooks.js +164 -0
  22. package/dist/index.d.ts +46 -0
  23. package/dist/index.js +52 -355
  24. package/dist/jsx-runtime/index.d.ts +9 -0
  25. package/dist/jsx-runtime/index.js +16 -0
  26. package/dist/jsx-runtime/jsx-dev-runtime.js +1 -0
  27. package/dist/jsx-runtime/jsx-runtime.js +91 -0
  28. package/dist/{src/jsx-runtime.d.ts → jsx-runtime.d.ts} +1 -1
  29. package/dist/jsx-runtime.js +192 -0
  30. package/dist/renderer.js +51 -0
  31. package/dist/{src/server-renderer.d.ts → server-renderer.d.ts} +3 -0
  32. package/dist/server-renderer.js +102 -0
  33. package/dist/vdom.js +27 -0
  34. package/package.json +40 -53
  35. package/scripts/build-cli.js +81 -0
  36. package/scripts/generate.js +134 -0
  37. package/src/backend/api-utils.ts +178 -0
  38. package/src/backend/auth.ts +543 -0
  39. package/src/backend/database.ts +104 -0
  40. package/src/backend/model.ts +196 -0
  41. package/src/backend/router.ts +176 -0
  42. package/src/backend/server.ts +330 -0
  43. package/src/backend/types.ts +257 -0
  44. package/src/batch.ts +24 -0
  45. package/src/cli/index.js +22 -40
  46. package/src/cli/index.ts +251 -0
  47. package/src/component.ts +98 -0
  48. package/src/components/Counter.tsx +4 -0
  49. package/src/context.ts +32 -0
  50. package/src/hooks.ts +211 -0
  51. package/src/index.ts +113 -0
  52. package/src/jsx-runtime/index.ts +24 -0
  53. package/src/jsx-runtime/jsx-runtime.ts +99 -0
  54. package/src/jsx-runtime.ts +226 -0
  55. package/src/renderer.ts +55 -0
  56. package/src/server-renderer.ts +114 -0
  57. package/src/types/bcrypt.d.ts +30 -0
  58. package/src/types/jsonwebtoken.d.ts +55 -0
  59. package/src/types.d.ts +26 -0
  60. package/src/types.ts +21 -0
  61. package/src/vdom.ts +34 -0
  62. package/templates/basic-app/package.json +17 -15
  63. package/templates/basic-app/postcss.config.js +1 -0
  64. package/templates/basic-app/src/App.tsx +65 -0
  65. package/templates/basic-app/src/api.ts +58 -0
  66. package/templates/basic-app/src/components/Counter.tsx +26 -0
  67. package/templates/basic-app/src/components/Header.tsx +9 -0
  68. package/templates/basic-app/src/components/TodoList.tsx +90 -0
  69. package/templates/basic-app/src/main.ts +20 -0
  70. package/templates/basic-app/src/server.ts +99 -0
  71. package/templates/basic-app/tailwind.config.js +23 -2
  72. package/templates/full-stack/index.html +13 -0
  73. package/templates/full-stack/package.json +25 -18
  74. package/templates/full-stack/src/main.css +3 -0
  75. package/templates/full-stack/src/main.ts +20 -0
  76. package/bin/cli.js +0 -371
  77. package/dist/index.js.map +0 -1
  78. package/dist/index.mjs +0 -139269
  79. package/dist/index.mjs.map +0 -1
  80. package/dist/src/index.d.ts +0 -16
  81. package/dist/test/setupTests.d.ts +0 -4
  82. /package/dist/{src/backend → backend}/auth.d.ts +0 -0
  83. /package/dist/{src/backend → backend}/server.d.ts +0 -0
  84. /package/dist/{src/backend → backend}/types.d.ts +0 -0
  85. /package/dist/{test/backend.test.d.ts → backend/types.js} +0 -0
  86. /package/dist/{src/batch.d.ts → batch.d.ts} +0 -0
  87. /package/dist/{src/cli → cli}/index.d.ts +0 -0
  88. /package/dist/{src/components → components}/Counter.d.ts +0 -0
  89. /package/dist/{src/context.d.ts → context.d.ts} +0 -0
  90. /package/dist/{src/hooks.d.ts → hooks.d.ts} +0 -0
  91. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-dev-runtime.d.ts +0 -0
  92. /package/dist/{src/jsx-runtime → jsx-runtime}/jsx-runtime.d.ts +0 -0
  93. /package/dist/{src/renderer.d.ts → renderer.d.ts} +0 -0
  94. /package/dist/{src/types.d.ts → types.d.ts} +0 -0
  95. /package/dist/{test/mockTest.d.ts → types.js} +0 -0
  96. /package/dist/{src/vdom.d.ts → vdom.d.ts} +0 -0
  97. /package/{dist/test/mongooseSetup.d.ts → src/jsx-runtime/jsx-dev-runtime.ts} +0 -0
@@ -0,0 +1,257 @@
1
+ import { Request, Response, NextFunction, RequestHandler } from 'express';
2
+ import { DatabaseConnector } from './database';
3
+
4
+ /**
5
+ * Options for configuring a Hamroun server
6
+ */
7
+ export interface HamrounServerOptions {
8
+ /**
9
+ * Port to run the server on (default: 3000)
10
+ */
11
+ port?: number;
12
+
13
+ /**
14
+ * Directory to serve static files from (default: 'public')
15
+ */
16
+ staticDir?: string;
17
+
18
+ /**
19
+ * Enable CORS headers (default: true)
20
+ */
21
+ enableCors?: boolean;
22
+
23
+ /**
24
+ * Prefix for API routes (default: '/api')
25
+ */
26
+ apiPrefix?: string;
27
+
28
+ /**
29
+ * Enable server-side rendering (default: true)
30
+ */
31
+ ssrEnabled?: boolean;
32
+
33
+ /**
34
+ * Additional Express middleware to apply
35
+ */
36
+ middlewares?: RequestHandler[];
37
+
38
+ /**
39
+ * Enable response compression (default: true)
40
+ */
41
+ enableCompression?: boolean;
42
+
43
+ /**
44
+ * Enable helmet security headers (default: true)
45
+ */
46
+ enableHelmet?: boolean;
47
+
48
+ /**
49
+ * Disable Content Security Policy (default: false)
50
+ */
51
+ disableCSP?: boolean;
52
+
53
+ /**
54
+ * Logging format for Morgan (default: 'dev')
55
+ */
56
+ logFormat?: string | null;
57
+
58
+ /**
59
+ * Trust proxy headers (default: false)
60
+ */
61
+ trustProxy?: boolean | string | number | string[] | number[];
62
+
63
+ /**
64
+ * Static file cache age (default: '1d')
65
+ */
66
+ staticCacheAge?: string;
67
+
68
+ /**
69
+ * Show detailed error information in responses (default: false)
70
+ */
71
+ showErrorDetails?: boolean;
72
+ }
73
+
74
+ /**
75
+ * Options for router configuration
76
+ */
77
+ export interface RouterOptions {
78
+ /**
79
+ * Prefix to apply to all routes (overrides server apiPrefix)
80
+ */
81
+ prefix?: string;
82
+
83
+ /**
84
+ * Enable authentication for all routes in this router
85
+ */
86
+ requireAuth?: boolean;
87
+
88
+ /**
89
+ * Required role for all routes in this router
90
+ */
91
+ requiredRole?: string | string[];
92
+
93
+ /**
94
+ * Enable rate limiting for this router
95
+ */
96
+ rateLimit?: {
97
+ windowMs: number;
98
+ max: number;
99
+ message?: string;
100
+ };
101
+ }
102
+
103
+ /**
104
+ * Context provided to route handlers
105
+ */
106
+ export interface RouteContext {
107
+ req: Request;
108
+ res: Response;
109
+ next: NextFunction;
110
+ params: any;
111
+ query: any;
112
+ body: any;
113
+ user?: any;
114
+ }
115
+
116
+ /**
117
+ * Pagination options
118
+ */
119
+ export interface PaginationOptions {
120
+ page: number;
121
+ limit: number;
122
+ sort?: string;
123
+ order?: 'asc' | 'desc';
124
+ }
125
+
126
+ /**
127
+ * Paginated result
128
+ */
129
+ export interface PaginatedResult<T> {
130
+ data: T[];
131
+ pagination: {
132
+ total: number;
133
+ totalPages: number;
134
+ currentPage: number;
135
+ limit: number;
136
+ hasNextPage: boolean;
137
+ hasPrevPage: boolean;
138
+ };
139
+ }
140
+
141
+ /**
142
+ * Database connection options
143
+ */
144
+ export interface DatabaseOptions {
145
+ /**
146
+ * URI to connect to the database
147
+ */
148
+ uri: string;
149
+
150
+ /**
151
+ * Database name
152
+ */
153
+ name: string;
154
+
155
+ /**
156
+ * Connection options
157
+ */
158
+ options?: Record<string, any>;
159
+
160
+ /**
161
+ * Number of retry attempts (default: 3)
162
+ */
163
+ retryAttempts?: number;
164
+
165
+ /**
166
+ * Retry delay in ms (default: 1000)
167
+ */
168
+ retryDelay?: number;
169
+
170
+ /**
171
+ * Connection timeout in ms (default: 10000)
172
+ */
173
+ connectionTimeout?: number;
174
+
175
+ /**
176
+ * Auto-index creation (default: true)
177
+ */
178
+ autoIndex?: boolean;
179
+ }
180
+
181
+ /**
182
+ * Data model structure
183
+ */
184
+ export interface Model<T> {
185
+ /**
186
+ * Get all documents
187
+ */
188
+ getAll: (options?: PaginationOptions) => Promise<PaginatedResult<T>>;
189
+
190
+ /**
191
+ * Get document by ID
192
+ */
193
+ getById: (id: string) => Promise<T | null>;
194
+
195
+ /**
196
+ * Create a new document
197
+ */
198
+ create: (data: Partial<T>) => Promise<T>;
199
+
200
+ /**
201
+ * Create multiple documents
202
+ */
203
+ createMany: (data: Partial<T>[]) => Promise<T[]>;
204
+
205
+ /**
206
+ * Update a document
207
+ */
208
+ update: (id: string, data: Partial<T>) => Promise<T | null>;
209
+
210
+ /**
211
+ * Delete a document
212
+ */
213
+ delete: (id: string) => Promise<boolean>;
214
+
215
+ /**
216
+ * Find documents by custom query
217
+ */
218
+ find: (query: Record<string, any>, options?: PaginationOptions) => Promise<PaginatedResult<T>>;
219
+
220
+ /**
221
+ * Count documents matching query
222
+ */
223
+ count: (query?: Record<string, any>) => Promise<number>;
224
+
225
+ /**
226
+ * Find one document
227
+ */
228
+ findOne: (query: Record<string, any>) => Promise<T | null>;
229
+ }
230
+
231
+ /**
232
+ * API response format
233
+ */
234
+ export interface ApiResponse<T = any> {
235
+ success: boolean;
236
+ data?: T;
237
+ error?: string;
238
+ message?: string;
239
+ meta?: Record<string, any>;
240
+ }
241
+
242
+ // Extend Express Application interface
243
+ declare global {
244
+ namespace Express {
245
+ interface Application {
246
+ registerApi: (routePath: string, router: any, options?: RouterOptions) => Application;
247
+ registerSSR: (routePath: string, component: any, options?: any) => Application;
248
+ start: (callback?: () => void) => any;
249
+ connectToDatabase: (dbOptions: DatabaseOptions) => Promise<DatabaseConnector>;
250
+ }
251
+
252
+ interface Request {
253
+ user?: any;
254
+ pagination?: PaginationOptions;
255
+ }
256
+ }
257
+ }
package/src/batch.ts ADDED
@@ -0,0 +1,24 @@
1
+ export let isBatching = false;
2
+ const queue: Function[] = [];
3
+
4
+ export function batchUpdates(fn: Function) {
5
+ if (isBatching) {
6
+ queue.push(fn);
7
+ return;
8
+ }
9
+
10
+ isBatching = true;
11
+ try {
12
+ fn();
13
+ while (queue.length > 0) {
14
+ const nextFn = queue.shift();
15
+ nextFn?.();
16
+ }
17
+ } finally {
18
+ isBatching = false;
19
+ }
20
+ }
21
+
22
+ export function getIsBatching() {
23
+ return isBatching;
24
+ }
package/src/cli/index.js CHANGED
@@ -1,60 +1,42 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const fs = require('fs');
4
- const path = require('path');
5
- const { exec } = require('child_process');
6
- const readline = require('readline');
7
- const { promisify } = require('util');
8
- const copyFile = promisify(fs.copyFile);
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import readline from 'readline';
6
+ import { exec } from 'child_process';
7
+ import { promisify } from 'util';
8
+
9
+ // Promisify fs functions
9
10
  const mkdir = promisify(fs.mkdir);
10
- const access = promisify(fs.access);
11
11
  const readdir = promisify(fs.readdir);
12
12
  const writeFile = promisify(fs.writeFile);
13
13
  const readFile = promisify(fs.readFile);
14
+ const copyFile = promisify(fs.copyFile);
15
+ const access = promisify(fs.access);
16
+ const execPromise = promisify(exec);
14
17
 
18
+ // Find the templates directory
19
+ const TEMPLATES_DIR = path.join(path.dirname(new URL(import.meta.url).pathname), '..', 'templates');
20
+
21
+ // Create readline interface
15
22
  const rl = readline.createInterface({
16
23
  input: process.stdin,
17
24
  output: process.stdout
18
25
  });
19
26
 
27
+ // Promisify question
20
28
  const question = (query) => new Promise((resolve) => rl.question(query, resolve));
21
29
 
22
- const TEMPLATES_DIR = path.join(__dirname, '..', '..', 'templates');
23
- const COLORS = {
24
- reset: '\x1b[0m',
25
- green: '\x1b[32m',
26
- yellow: '\x1b[33m',
27
- blue: '\x1b[34m',
28
- red: '\x1b[31m',
29
- cyan: '\x1b[36m'
30
- };
31
-
32
- /**
33
- * Format colored console log
34
- */
30
+ // Simple logger
35
31
  const log = {
36
- info: (msg) => console.log(`${COLORS.blue}ℹ ${COLORS.reset}${msg}`),
37
- success: (msg) => console.log(`${COLORS.green}✓ ${COLORS.reset}${msg}`),
38
- warn: (msg) => console.log(`${COLORS.yellow}⚠ ${COLORS.reset}${msg}`),
39
- error: (msg) => console.log(`${COLORS.red}✗ ${COLORS.reset}${msg}`),
40
- title: (msg) => console.log(`\n${COLORS.cyan}${msg}${COLORS.reset}\n`)
32
+ info: (msg) => console.log(`\x1b[36minfo\x1b[0m: ${msg}`),
33
+ success: (msg) => console.log(`\x1b[32msuccess\x1b[0m: ${msg}`),
34
+ warn: (msg) => console.log(`\x1b[33mwarning\x1b[0m: ${msg}`),
35
+ error: (msg) => console.log(`\x1b[31merror\x1b[0m: ${msg}`),
36
+ title: (msg) => console.log(`\n\x1b[1m${msg}\x1b[0m\n`)
41
37
  };
42
38
 
43
- /**
44
- * Execute shell command
45
- */
46
- const execPromise = (cmd, options = {}) => {
47
- return new Promise((resolve, reject) => {
48
- const process = exec(cmd, options, (error, stdout) => {
49
- if (error) {
50
- reject(error);
51
- return;
52
- }
53
- resolve(stdout);
54
- });
55
- });
56
- }
57
-
39
+ // Rest of the CLI functionality...
58
40
  /**
59
41
  * Copy directory recursively
60
42
  */
@@ -0,0 +1,251 @@
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
+ // Available templates
17
+ const TEMPLATES = {
18
+ 'basic': 'Basic frontend only template',
19
+ 'full-stack': 'Complete frontend and backend template',
20
+ 'api-only': 'Backend API only template'
21
+ };
22
+
23
+ // CLI colors
24
+ const colors = {
25
+ reset: '\x1b[0m',
26
+ bright: '\x1b[1m',
27
+ green: '\x1b[32m',
28
+ blue: '\x1b[34m',
29
+ red: '\x1b[31m',
30
+ yellow: '\x1b[33m'
31
+ };
32
+
33
+ // Create readline interface
34
+ function createInterface() {
35
+ return readline.createInterface({
36
+ input: process.stdin,
37
+ output: process.stdout
38
+ });
39
+ }
40
+
41
+ // Print banner
42
+ function printBanner() {
43
+ console.log(`
44
+ ${colors.blue}${colors.bright}╔══════════════════════════════════════════════╗
45
+ ║ ║
46
+ ║ Frontend Hamroun v${packageJson.version.padEnd(25)}║
47
+ ║ A lightweight frontend & backend framework ║
48
+ ║ ║
49
+ ╚══════════════════════════════════════════════╝${colors.reset}
50
+ `);
51
+ }
52
+
53
+ // Print help
54
+ function printHelp() {
55
+ console.log(`
56
+ ${colors.bright}USAGE:${colors.reset}
57
+ ${colors.blue}npx frontend-hamroun${colors.reset} [command] [options]
58
+
59
+ ${colors.bright}COMMANDS:${colors.reset}
60
+ ${colors.blue}create${colors.reset} <project-name> [options] Create a new project
61
+ ${colors.blue}help${colors.reset} Display this help message
62
+ ${colors.blue}version${colors.reset} Display version information
63
+
64
+ ${colors.bright}OPTIONS:${colors.reset}
65
+ ${colors.blue}--template${colors.reset}, ${colors.blue}-t${colors.reset} <template> Specify template (default: basic)
66
+ ${colors.blue}--skip-install${colors.reset}, ${colors.blue}-s${colors.reset} Skip package installation
67
+
68
+ ${colors.bright}AVAILABLE TEMPLATES:${colors.reset}
69
+ ${Object.entries(TEMPLATES).map(([name, desc]) => ` ${colors.blue}${name.padEnd(12)}${colors.reset} ${desc}`).join('\n')}
70
+
71
+ ${colors.bright}EXAMPLES:${colors.reset}
72
+ ${colors.blue}npx frontend-hamroun create${colors.reset} my-app
73
+ ${colors.blue}npx frontend-hamroun create${colors.reset} my-app --template full-stack
74
+ ${colors.blue}npx frontend-hamroun create${colors.reset} my-api -t api-only --skip-install
75
+ `);
76
+ }
77
+
78
+ // Create project from template
79
+ async function createProject(projectName, options) {
80
+ const template = options.template || 'basic';
81
+
82
+ // Check if template exists
83
+ if (!Object.keys(TEMPLATES).includes(template)) {
84
+ console.error(`${colors.red}error:${colors.reset} Template "${template}" not found.`);
85
+ console.log(`Available templates: ${Object.keys(TEMPLATES).join(', ')}`);
86
+ process.exit(1);
87
+ }
88
+
89
+ // Check if project directory already exists
90
+ const projectPath = path.resolve(process.cwd(), projectName);
91
+ if (fs.existsSync(projectPath)) {
92
+ console.error(`${colors.red}error:${colors.reset} Directory ${projectName} already exists.`);
93
+ process.exit(1);
94
+ }
95
+
96
+ console.log(`
97
+ ${colors.bright}Creating a new project with Frontend Hamroun...${colors.reset}
98
+ ${colors.blue}• Project name:${colors.reset} ${projectName}
99
+ ${colors.blue}• Template:${colors.reset} ${template}
100
+ ${colors.blue}• Directory:${colors.reset} ${projectPath}
101
+ `);
102
+
103
+ try {
104
+ // Find templates directory
105
+ const templateDir = path.resolve(__dirname, '../../templates', template);
106
+
107
+ // Create project directory
108
+ fs.mkdirSync(projectPath, { recursive: true });
109
+
110
+ // Copy template files
111
+ copyTemplateFiles(templateDir, projectPath);
112
+
113
+ // Update package.json with project name
114
+ const projectPackageJsonPath = path.join(projectPath, 'package.json');
115
+ if (fs.existsSync(projectPackageJsonPath)) {
116
+ const projectPackageJson = JSON.parse(fs.readFileSync(projectPackageJsonPath, 'utf8'));
117
+ projectPackageJson.name = projectName;
118
+ fs.writeFileSync(
119
+ projectPackageJsonPath,
120
+ JSON.stringify(projectPackageJson, null, 2)
121
+ );
122
+ }
123
+
124
+ // Install dependencies
125
+ if (!options.skipInstall) {
126
+ console.log(`\n${colors.blue}Installing dependencies...${colors.reset}`);
127
+
128
+ const command = getPackageManager() === 'yarn'
129
+ ? 'yarn'
130
+ : 'npm install';
131
+
132
+ execSync(command, {
133
+ cwd: projectPath,
134
+ stdio: 'inherit'
135
+ });
136
+ }
137
+
138
+ // Success message
139
+ console.log(`
140
+ ${colors.green}${colors.bright}Success!${colors.reset} Created ${projectName} at ${projectPath}
141
+
142
+ ${colors.blue}Inside that directory, you can run several commands:${colors.reset}
143
+
144
+ ${colors.bright}npm run dev${colors.reset}
145
+ Starts the development server.
146
+
147
+ ${colors.bright}npm run build${colors.reset}
148
+ Builds the app for production.
149
+
150
+ ${colors.bright}npm start${colors.reset}
151
+ Runs the built app in production mode.
152
+
153
+ ${colors.blue}We suggest that you begin by typing:${colors.reset}
154
+
155
+ ${colors.bright}cd${colors.reset} ${projectName}
156
+ ${colors.bright}npm run dev${colors.reset}
157
+
158
+ ${colors.green}Happy coding!${colors.reset}
159
+ `);
160
+
161
+ } catch (error) {
162
+ console.error(`${colors.red}Failed to create project:${colors.reset}`, error);
163
+ process.exit(1);
164
+ }
165
+ }
166
+
167
+ // Copy template files recursively
168
+ function copyTemplateFiles(source, destination) {
169
+ const files = fs.readdirSync(source);
170
+
171
+ for (const file of files) {
172
+ const sourcePath = path.join(source, file);
173
+ const destPath = path.join(destination, file);
174
+
175
+ const stats = fs.statSync(sourcePath);
176
+
177
+ if (stats.isDirectory()) {
178
+ fs.mkdirSync(destPath, { recursive: true });
179
+ copyTemplateFiles(sourcePath, destPath);
180
+ } else {
181
+ fs.copyFileSync(sourcePath, destPath);
182
+ }
183
+ }
184
+
185
+ console.log(`${colors.green}•${colors.reset} Copied template files`);
186
+ }
187
+
188
+ // Detect package manager
189
+ function getPackageManager() {
190
+ try {
191
+ const userAgent = process.env.npm_config_user_agent;
192
+ if (userAgent && userAgent.startsWith('yarn')) {
193
+ return 'yarn';
194
+ }
195
+ return 'npm';
196
+ } catch (error) {
197
+ return 'npm';
198
+ }
199
+ }
200
+
201
+ // Parse command line arguments
202
+ function parseArgs() {
203
+ const args = process.argv.slice(2);
204
+ const command = args[0];
205
+
206
+ if (!command || command === 'help') {
207
+ printBanner();
208
+ printHelp();
209
+ process.exit(0);
210
+ }
211
+
212
+ if (command === 'version') {
213
+ console.log(`frontend-hamroun v${packageJson.version}`);
214
+ process.exit(0);
215
+ }
216
+
217
+ if (command === 'create') {
218
+ const projectName = args[1];
219
+
220
+ if (!projectName) {
221
+ console.error(`${colors.red}error:${colors.reset} Project name is required.`);
222
+ console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
223
+ process.exit(1);
224
+ }
225
+
226
+ // Parse options
227
+ const options = {
228
+ template: 'basic',
229
+ skipInstall: false
230
+ };
231
+
232
+ for (let i = 2; i < args.length; i++) {
233
+ if (args[i] === '--template' || args[i] === '-t') {
234
+ options.template = args[++i];
235
+ } else if (args[i] === '--skip-install' || args[i] === '-s') {
236
+ options.skipInstall = true;
237
+ }
238
+ }
239
+
240
+ printBanner();
241
+ createProject(projectName, options);
242
+ return;
243
+ }
244
+
245
+ console.error(`${colors.red}error:${colors.reset} Unknown command: ${command}`);
246
+ console.log(`Run ${colors.blue}npx frontend-hamroun help${colors.reset} for usage information.`);
247
+ process.exit(1);
248
+ }
249
+
250
+ // Run CLI
251
+ parseArgs();
@@ -0,0 +1,98 @@
1
+ import { createElement } from './jsx-runtime';
2
+
3
+ export class Component {
4
+ state: any = {};
5
+ props: any;
6
+ element: HTMLElement | null = null;
7
+ private _mounted: boolean = false;
8
+
9
+ constructor(props: any = {}) {
10
+ this.props = props;
11
+ }
12
+
13
+ componentDidMount() {
14
+ // Hook for after component is mounted
15
+ }
16
+
17
+ async setState(newState: any) {
18
+ const prevState = { ...this.state };
19
+ this.state = { ...prevState, ...newState };
20
+ console.log(`${this.constructor.name} state updated:`, {
21
+ prev: prevState,
22
+ next: this.state
23
+ });
24
+
25
+ await Promise.resolve(); // Ensure state is updated before re-render
26
+ if (this._mounted) {
27
+ await this.update();
28
+ } else {
29
+ await this.update();
30
+ }
31
+ }
32
+
33
+ private _replayEvents(oldElement: HTMLElement, newElement: HTMLElement) {
34
+ const oldEvents = (oldElement as any).__events || {};
35
+ Object.entries(oldEvents).forEach(([event, handler]) => {
36
+ newElement.addEventListener(event as keyof HTMLElementEventMap, handler as EventListener);
37
+ });
38
+ (newElement as any).__events = oldEvents;
39
+ }
40
+
41
+ private _deepCloneWithEvents(node: HTMLElement): HTMLElement {
42
+ const clone = node.cloneNode(false) as HTMLElement;
43
+
44
+ // Copy events from original element
45
+ const events = (node as any).__events || {};
46
+ (clone as any).__events = events;
47
+ Object.entries(events).forEach(([event, handler]) => {
48
+ clone.addEventListener(event as keyof HTMLElementEventMap, handler as EventListener);
49
+ });
50
+
51
+ // Clone children
52
+ Array.from(node.childNodes).forEach(child => {
53
+ if (child instanceof HTMLElement) {
54
+ clone.appendChild(this._deepCloneWithEvents(child));
55
+ } else {
56
+ clone.appendChild(child.cloneNode(true));
57
+ }
58
+ });
59
+
60
+ return clone;
61
+ }
62
+
63
+ async update() {
64
+ const vdom = this.render();
65
+ if (!vdom) return document.createTextNode('');
66
+
67
+ const rendered = await createElement(vdom);
68
+ if (rendered instanceof HTMLElement) {
69
+ return this._updateElement(rendered);
70
+ }
71
+
72
+ const wrapper = document.createElement('div');
73
+ wrapper.appendChild(rendered);
74
+ return this._updateElement(wrapper);
75
+ }
76
+
77
+ private async _updateElement(rendered: HTMLElement) {
78
+ const newElement = this._deepCloneWithEvents(rendered);
79
+ (newElement as any).__instance = this;
80
+
81
+ if (!this.element) {
82
+ this.element = newElement;
83
+ if (!this._mounted) {
84
+ this._mounted = true;
85
+ queueMicrotask(() => this.componentDidMount());
86
+ }
87
+ } else if (this.element.parentNode) {
88
+ this.element.parentNode.replaceChild(newElement, this.element);
89
+ this.element = newElement;
90
+ }
91
+
92
+ return this.element;
93
+ }
94
+
95
+ render(): any {
96
+ throw new Error('Component must implement render() method');
97
+ }
98
+ }