frontend-hamroun 1.2.2 → 1.2.3
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.cjs +16 -0
- package/bin/cli.mjs +237 -0
- package/package.json +3 -3
- package/scripts/build-cli.js +409 -121
- package/templates/basic/.eslintignore +5 -0
- package/templates/basic/.eslintrc.json +25 -0
- package/templates/basic/docs/rapport_pfe.aux +27 -0
- package/templates/basic/docs/rapport_pfe.log +399 -0
- package/templates/basic/docs/rapport_pfe.out +10 -0
- package/templates/basic/docs/rapport_pfe.pdf +0 -0
- package/templates/basic/docs/rapport_pfe.tex +68 -0
- package/templates/basic/docs/rapport_pfe.toc +14 -0
- package/templates/basic/index.html +12 -0
- package/templates/basic/package.json +30 -0
- package/templates/basic/postcss.config.js +7 -0
- package/templates/basic/src/App.tsx +65 -0
- package/templates/basic/src/api.ts +58 -0
- package/templates/basic/src/components/Counter.tsx +26 -0
- package/templates/basic/src/components/Header.tsx +9 -0
- package/templates/basic/src/components/TodoList.tsx +90 -0
- package/templates/basic/src/main.css +3 -0
- package/templates/basic/src/main.ts +20 -0
- package/templates/basic/src/main.tsx +144 -0
- package/templates/basic/src/server.ts +99 -0
- package/templates/basic/tailwind.config.js +32 -0
- package/templates/basic/tsconfig.json +13 -0
- package/templates/basic/vite.config.ts +28 -0
- package/templates/basic-app/vite.config.ts +6 -0
- package/bin/cli.js +0 -16
@@ -0,0 +1,26 @@
|
|
1
|
+
import { useState } from 'front-package';
|
2
|
+
|
3
|
+
export function Counter() {
|
4
|
+
const [count, setCount] = useState(0);
|
5
|
+
|
6
|
+
return (
|
7
|
+
<div className="text-center">
|
8
|
+
<h2 className="text-xl font-bold mb-4">Counter Example</h2>
|
9
|
+
<p className="mb-4">Count: <span className="font-bold">{count}</span></p>
|
10
|
+
<div className="flex justify-center gap-2">
|
11
|
+
<button
|
12
|
+
onClick={() => setCount(count - 1)}
|
13
|
+
className="px-4 py-2 bg-red-500 text-white rounded hover:bg-red-600 transition"
|
14
|
+
>
|
15
|
+
Decrement
|
16
|
+
</button>
|
17
|
+
<button
|
18
|
+
onClick={() => setCount(count + 1)}
|
19
|
+
className="px-4 py-2 bg-blue-500 text-white rounded hover:bg-blue-600 transition"
|
20
|
+
>
|
21
|
+
Increment
|
22
|
+
</button>
|
23
|
+
</div>
|
24
|
+
</div>
|
25
|
+
);
|
26
|
+
}
|
@@ -0,0 +1,90 @@
|
|
1
|
+
import { useState } from 'front-package';
|
2
|
+
import { ApiClient, Todo } from '../api';
|
3
|
+
|
4
|
+
interface TodoListProps {
|
5
|
+
todos: Todo[];
|
6
|
+
api: ApiClient;
|
7
|
+
setTodos: (todos: Todo[]) => void;
|
8
|
+
}
|
9
|
+
|
10
|
+
export function TodoList({ todos, api, setTodos }: TodoListProps) {
|
11
|
+
const [newTodoText, setNewTodoText] = useState('');
|
12
|
+
const [isSubmitting, setIsSubmitting] = useState(false);
|
13
|
+
|
14
|
+
async function handleAddTodo(e: Event) {
|
15
|
+
e.preventDefault();
|
16
|
+
if (!newTodoText.trim()) return;
|
17
|
+
|
18
|
+
try {
|
19
|
+
setIsSubmitting(true);
|
20
|
+
const newTodo = await api.addTodo({
|
21
|
+
title: newTodoText,
|
22
|
+
completed: false
|
23
|
+
});
|
24
|
+
|
25
|
+
setTodos([...todos, newTodo]);
|
26
|
+
setNewTodoText('');
|
27
|
+
} catch (err) {
|
28
|
+
console.error('Failed to add todo:', err);
|
29
|
+
} finally {
|
30
|
+
setIsSubmitting(false);
|
31
|
+
}
|
32
|
+
}
|
33
|
+
|
34
|
+
async function handleToggleTodo(id: string, completed: boolean) {
|
35
|
+
try {
|
36
|
+
await api.updateTodo(id, { completed });
|
37
|
+
setTodos(
|
38
|
+
todos.map(todo =>
|
39
|
+
todo.id === id ? { ...todo, completed } : todo
|
40
|
+
)
|
41
|
+
);
|
42
|
+
} catch (err) {
|
43
|
+
console.error('Failed to update todo:', err);
|
44
|
+
}
|
45
|
+
}
|
46
|
+
|
47
|
+
return (
|
48
|
+
<div>
|
49
|
+
<form onSubmit={handleAddTodo} className="mb-4">
|
50
|
+
<div className="flex gap-2">
|
51
|
+
<input
|
52
|
+
type="text"
|
53
|
+
value={newTodoText}
|
54
|
+
onChange={(e) => setNewTodoText(e.target.value)}
|
55
|
+
placeholder="Add a new todo..."
|
56
|
+
className="flex-1 border rounded px-3 py-2"
|
57
|
+
disabled={isSubmitting}
|
58
|
+
/>
|
59
|
+
<button
|
60
|
+
type="submit"
|
61
|
+
className="px-4 py-2 bg-green-500 text-white rounded hover:bg-green-600 transition"
|
62
|
+
disabled={isSubmitting || !newTodoText.trim()}
|
63
|
+
>
|
64
|
+
{isSubmitting ? 'Adding...' : 'Add'}
|
65
|
+
</button>
|
66
|
+
</div>
|
67
|
+
</form>
|
68
|
+
|
69
|
+
<ul className="space-y-2">
|
70
|
+
{todos.length === 0 ? (
|
71
|
+
<li className="text-center py-2 text-gray-500">No todos yet</li>
|
72
|
+
) : (
|
73
|
+
todos.map(todo => (
|
74
|
+
<li key={todo.id} className="flex items-center gap-2 p-2 border-b">
|
75
|
+
<input
|
76
|
+
type="checkbox"
|
77
|
+
checked={todo.completed}
|
78
|
+
onChange={(e) => handleToggleTodo(todo.id, e.target.checked)}
|
79
|
+
className="h-5 w-5 text-blue-600"
|
80
|
+
/>
|
81
|
+
<span className={todo.completed ? 'line-through text-gray-500' : ''}>
|
82
|
+
{todo.title}
|
83
|
+
</span>
|
84
|
+
</li>
|
85
|
+
))
|
86
|
+
)}
|
87
|
+
</ul>
|
88
|
+
</div>
|
89
|
+
);
|
90
|
+
}
|
@@ -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: import.meta.env.VITE_API_URL || '/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
|
+
});
|
@@ -0,0 +1,144 @@
|
|
1
|
+
import { render, useState, useEffect, useMemo, useRef, useErrorBoundary, createContext, useContext } from 'frontend-hamroun';
|
2
|
+
import './main.css';
|
3
|
+
|
4
|
+
// Create a theme context
|
5
|
+
const ThemeContext = createContext<'light' | 'dark'>('light');
|
6
|
+
|
7
|
+
// Create a component that will throw an error when clicked
|
8
|
+
function BuggyComponent() {
|
9
|
+
const [shouldError, setShouldError] = useState(false);
|
10
|
+
|
11
|
+
if (shouldError) {
|
12
|
+
throw new Error("Test error from BuggyComponent");
|
13
|
+
}
|
14
|
+
|
15
|
+
return (
|
16
|
+
<button
|
17
|
+
onClick={() => setShouldError(true)}
|
18
|
+
className="bg-red-600 text-white px-4 py-2 rounded mt-4 hover:bg-red-700"
|
19
|
+
>
|
20
|
+
Trigger Error
|
21
|
+
</button>
|
22
|
+
);
|
23
|
+
}
|
24
|
+
|
25
|
+
function App() {
|
26
|
+
const [count, setCount] = useState(0);
|
27
|
+
const [theme, setTheme] = useState<'light' | 'dark'>('light');
|
28
|
+
const renderCount = useRef(0);
|
29
|
+
const [error, resetError] = useErrorBoundary();
|
30
|
+
|
31
|
+
// Demonstrate useEffect
|
32
|
+
useEffect(() => {
|
33
|
+
document.title = `Count: ${count}`;
|
34
|
+
renderCount.current += 1;
|
35
|
+
|
36
|
+
return () => {
|
37
|
+
console.log('Cleanup effect');
|
38
|
+
};
|
39
|
+
}, [count]);
|
40
|
+
|
41
|
+
// Demonstrate useMemo
|
42
|
+
const doubled = useMemo(() => count * 2, [count]);
|
43
|
+
|
44
|
+
if (error) {
|
45
|
+
return (
|
46
|
+
<div className="p-8 max-w-md mx-auto bg-red-50 rounded-lg shadow-lg">
|
47
|
+
<h1 className="text-2xl font-bold text-red-600 mb-4">Something went wrong!</h1>
|
48
|
+
<button
|
49
|
+
onClick={resetError}
|
50
|
+
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
|
51
|
+
>
|
52
|
+
Try again
|
53
|
+
</button>
|
54
|
+
</div>
|
55
|
+
);
|
56
|
+
}
|
57
|
+
|
58
|
+
return (
|
59
|
+
<ThemeContext.Provider value={theme}>
|
60
|
+
<div className={`p-8 max-w-4xl mx-auto rounded-lg shadow-lg transition-colors ${
|
61
|
+
theme === 'dark'
|
62
|
+
? 'bg-gray-800 text-white'
|
63
|
+
: 'bg-white text-gray-900'
|
64
|
+
}`}>
|
65
|
+
<h1 className="text-3xl font-bold mb-6">Welcome to Frontend Hamroun</h1>
|
66
|
+
|
67
|
+
<div className="mb-6">
|
68
|
+
<button
|
69
|
+
onClick={() => setCount(count - 1)}
|
70
|
+
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
|
71
|
+
>-</button>
|
72
|
+
<span className="mx-4 text-xl">{count}</span>
|
73
|
+
<button
|
74
|
+
onClick={() => setCount(count + 1)}
|
75
|
+
className="bg-blue-500 hover:bg-blue-600 text-white px-4 py-2 rounded"
|
76
|
+
>+</button>
|
77
|
+
</div>
|
78
|
+
|
79
|
+
<p className="mb-2 text-lg">Doubled value: <span className="font-semibold">{doubled}</span></p>
|
80
|
+
<p className="mb-4 text-lg">Render count: <span className="font-semibold">{renderCount.current}</span></p>
|
81
|
+
|
82
|
+
<button
|
83
|
+
onClick={() => setTheme(theme === 'light' ? 'dark' : 'light')}
|
84
|
+
className={`px-4 py-2 rounded mb-8 ${
|
85
|
+
theme === 'dark'
|
86
|
+
? 'bg-yellow-400 text-gray-900 hover:bg-yellow-300'
|
87
|
+
: 'bg-gray-800 text-white hover:bg-gray-700'
|
88
|
+
}`}
|
89
|
+
>
|
90
|
+
Toggle Theme ({theme})
|
91
|
+
</button>
|
92
|
+
|
93
|
+
<div className="mt-8 border-t pt-6">
|
94
|
+
<h2 className="text-2xl font-bold mb-4">Test Error Boundary</h2>
|
95
|
+
<BuggyComponent />
|
96
|
+
</div>
|
97
|
+
|
98
|
+
<div className="mt-8 border-t pt-6">
|
99
|
+
<h2 className="text-2xl font-bold mb-4">Test Context</h2>
|
100
|
+
<ContextConsumer />
|
101
|
+
</div>
|
102
|
+
</div>
|
103
|
+
</ThemeContext.Provider>
|
104
|
+
);
|
105
|
+
}
|
106
|
+
|
107
|
+
// Component to test context
|
108
|
+
function ContextConsumer() {
|
109
|
+
// Get the full context object for debugging
|
110
|
+
const themeContext = useContext(ThemeContext);
|
111
|
+
console.log('Theme context object:', themeContext);
|
112
|
+
|
113
|
+
// Access the current theme from the parent component instead
|
114
|
+
// This is a workaround until context is properly implemented
|
115
|
+
return (
|
116
|
+
<div className={`mt-4 p-4 rounded-md border ${
|
117
|
+
themeContext === 'dark' ? 'border-gray-600' : 'border-gray-300'
|
118
|
+
}`}>
|
119
|
+
<p className="mb-2"><strong>Note:</strong> Context API needs a fix in the framework.</p>
|
120
|
+
<p className="mb-1">Context object type: {typeof themeContext}</p>
|
121
|
+
<p className="mb-4">Context object keys: {Object.keys(themeContext as object).join(', ') || 'none'}</p>
|
122
|
+
|
123
|
+
<button
|
124
|
+
onClick={() => {
|
125
|
+
// Since we can't get the value directly from context,
|
126
|
+
// Let's add a button to demonstrate we can still use the Provider
|
127
|
+
const newTheme = document.body.style.backgroundColor === 'rgb(51, 51, 51)'
|
128
|
+
? 'light' : 'dark';
|
129
|
+
|
130
|
+
console.log('Manually changing theme to:', newTheme);
|
131
|
+
// This won't work yet, but demonstrates the intent
|
132
|
+
if (ThemeContext) {
|
133
|
+
console.log('Provider exists, would set value to:', newTheme);
|
134
|
+
}
|
135
|
+
}}
|
136
|
+
className="bg-purple-500 hover:bg-purple-600 text-white px-4 py-2 rounded"
|
137
|
+
>
|
138
|
+
Check Context Implementation
|
139
|
+
</button>
|
140
|
+
</div>
|
141
|
+
);
|
142
|
+
}
|
143
|
+
|
144
|
+
render(<App />, document.getElementById('root')!);
|
@@ -0,0 +1,99 @@
|
|
1
|
+
import { createServer, Router, Database, ApiRoute } from 'front-package';
|
2
|
+
import { fileURLToPath } from 'url';
|
3
|
+
import { dirname, join } from 'path';
|
4
|
+
import fs from 'fs';
|
5
|
+
|
6
|
+
// Setup paths for server
|
7
|
+
const __filename = fileURLToPath(import.meta.url);
|
8
|
+
const __dirname = dirname(__filename);
|
9
|
+
const DIST_PATH = process.env.NODE_ENV === 'production'
|
10
|
+
? join(__dirname, '..')
|
11
|
+
: join(__dirname, '../dist');
|
12
|
+
|
13
|
+
// Initialize database
|
14
|
+
const db = new Database({
|
15
|
+
filePath: join(__dirname, '../data/db.json'),
|
16
|
+
defaultData: {
|
17
|
+
todos: [
|
18
|
+
{ id: '1', title: 'Learn Front Package', completed: false },
|
19
|
+
{ id: '2', title: 'Build an app', completed: false },
|
20
|
+
{ id: '3', title: 'Deploy to production', completed: false }
|
21
|
+
]
|
22
|
+
}
|
23
|
+
});
|
24
|
+
|
25
|
+
// Create API routes
|
26
|
+
const apiRouter = new Router();
|
27
|
+
|
28
|
+
// GET /api/todos
|
29
|
+
apiRouter.get('/todos', async (req, res) => {
|
30
|
+
const todos = await db.getAll('todos');
|
31
|
+
res.json(todos);
|
32
|
+
});
|
33
|
+
|
34
|
+
// GET /api/todos/:id
|
35
|
+
apiRouter.get('/todos/:id', async (req, res) => {
|
36
|
+
const todo = await db.getById('todos', req.params.id);
|
37
|
+
if (!todo) {
|
38
|
+
return res.status(404).json({ error: 'Todo not found' });
|
39
|
+
}
|
40
|
+
res.json(todo);
|
41
|
+
});
|
42
|
+
|
43
|
+
// POST /api/todos
|
44
|
+
apiRouter.post('/todos', async (req, res) => {
|
45
|
+
const { title, completed = false } = req.body;
|
46
|
+
|
47
|
+
if (!title) {
|
48
|
+
return res.status(400).json({ error: 'Title is required' });
|
49
|
+
}
|
50
|
+
|
51
|
+
const newTodo = {
|
52
|
+
id: Date.now().toString(),
|
53
|
+
title,
|
54
|
+
completed
|
55
|
+
};
|
56
|
+
|
57
|
+
await db.insert('todos', newTodo);
|
58
|
+
res.status(201).json(newTodo);
|
59
|
+
});
|
60
|
+
|
61
|
+
// PATCH /api/todos/:id
|
62
|
+
apiRouter.patch('/todos/:id', async (req, res) => {
|
63
|
+
const todo = await db.getById('todos', req.params.id);
|
64
|
+
if (!todo) {
|
65
|
+
return res.status(404).json({ error: 'Todo not found' });
|
66
|
+
}
|
67
|
+
|
68
|
+
const updatedTodo = { ...todo, ...req.body };
|
69
|
+
await db.update('todos', req.params.id, updatedTodo);
|
70
|
+
res.json(updatedTodo);
|
71
|
+
});
|
72
|
+
|
73
|
+
// DELETE /api/todos/:id
|
74
|
+
apiRouter.delete('/todos/:id', async (req, res) => {
|
75
|
+
const todo = await db.getById('todos', req.params.id);
|
76
|
+
if (!todo) {
|
77
|
+
return res.status(404).json({ error: 'Todo not found' });
|
78
|
+
}
|
79
|
+
|
80
|
+
await db.delete('todos', req.params.id);
|
81
|
+
res.status(204).end();
|
82
|
+
});
|
83
|
+
|
84
|
+
// Create server
|
85
|
+
const server = createServer({
|
86
|
+
port: process.env.PORT ? parseInt(process.env.PORT) : 3000,
|
87
|
+
staticDir: DIST_PATH,
|
88
|
+
routes: [
|
89
|
+
new ApiRoute('/api', apiRouter)
|
90
|
+
]
|
91
|
+
});
|
92
|
+
|
93
|
+
// Start server
|
94
|
+
server.start().then(() => {
|
95
|
+
console.log(`Server running at http://localhost:${server.port}`);
|
96
|
+
}).catch(err => {
|
97
|
+
console.error('Failed to start server:', err);
|
98
|
+
process.exit(1);
|
99
|
+
});
|
@@ -0,0 +1,32 @@
|
|
1
|
+
/** @type {import('tailwindcss').Config} */
|
2
|
+
export default {
|
3
|
+
content: [
|
4
|
+
"./index.html",
|
5
|
+
"./src/**/*.{js,ts,jsx,tsx}",
|
6
|
+
],
|
7
|
+
theme: {
|
8
|
+
extend: {
|
9
|
+
colors: {
|
10
|
+
primary: {
|
11
|
+
50: '#f0f9ff',
|
12
|
+
100: '#e0f2fe',
|
13
|
+
200: '#bae6fd',
|
14
|
+
300: '#7dd3fc',
|
15
|
+
400: '#38bdf8',
|
16
|
+
500: '#0ea5e9',
|
17
|
+
600: '#0284c7',
|
18
|
+
700: '#0369a1',
|
19
|
+
800: '#075985',
|
20
|
+
900: '#0c4a6e',
|
21
|
+
},
|
22
|
+
},
|
23
|
+
fontFamily: {
|
24
|
+
sans: ['Inter var', 'sans-serif'],
|
25
|
+
},
|
26
|
+
},
|
27
|
+
},
|
28
|
+
plugins: [
|
29
|
+
require('@tailwindcss/forms'),
|
30
|
+
require('@tailwindcss/typography'),
|
31
|
+
],
|
32
|
+
}
|
@@ -0,0 +1,13 @@
|
|
1
|
+
{
|
2
|
+
"compilerOptions": {
|
3
|
+
"target": "ES2020",
|
4
|
+
"module": "ESNext",
|
5
|
+
"lib": ["ES2020", "DOM", "DOM.Iterable"],
|
6
|
+
"jsx": "preserve",
|
7
|
+
"moduleResolution": "bundler",
|
8
|
+
"esModuleInterop": true,
|
9
|
+
"strict": true,
|
10
|
+
"skipLibCheck": true
|
11
|
+
},
|
12
|
+
"include": ["src"]
|
13
|
+
}
|
@@ -0,0 +1,28 @@
|
|
1
|
+
import { defineConfig } from 'vite';
|
2
|
+
|
3
|
+
export default defineConfig({
|
4
|
+
esbuild: {
|
5
|
+
jsxFactory: 'jsx',
|
6
|
+
jsxFragment: 'Fragment'
|
7
|
+
},
|
8
|
+
build: {
|
9
|
+
outDir: 'dist',
|
10
|
+
emptyOutDir: true,
|
11
|
+
rollupOptions: {
|
12
|
+
// Mark testing dependencies as external so they won't be included in the bundle
|
13
|
+
external: [
|
14
|
+
'mock-aws-s3',
|
15
|
+
'aws-sdk',
|
16
|
+
'nock',
|
17
|
+
'jest',
|
18
|
+
'jest-mock',
|
19
|
+
'@testing-library/react',
|
20
|
+
'@testing-library/jest-dom'
|
21
|
+
]
|
22
|
+
}
|
23
|
+
},
|
24
|
+
server: {
|
25
|
+
port: 3000,
|
26
|
+
open: true
|
27
|
+
}
|
28
|
+
});
|
@@ -6,6 +6,8 @@ export default defineConfig({
|
|
6
6
|
jsxFragment: 'Fragment'
|
7
7
|
},
|
8
8
|
build: {
|
9
|
+
outDir: 'dist',
|
10
|
+
emptyOutDir: true,
|
9
11
|
rollupOptions: {
|
10
12
|
// Mark testing dependencies as external so they won't be included in the bundle
|
11
13
|
external: [
|
@@ -18,5 +20,9 @@ export default defineConfig({
|
|
18
20
|
'@testing-library/jest-dom'
|
19
21
|
]
|
20
22
|
}
|
23
|
+
},
|
24
|
+
server: {
|
25
|
+
port: 3000,
|
26
|
+
open: true
|
21
27
|
}
|
22
28
|
});
|
package/bin/cli.js
DELETED
@@ -1,16 +0,0 @@
|
|
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
|
-
}
|