create-aziz-app 1.0.3 ā 2.0.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/create-aziz-app.js +39 -20
- package/package.json +3 -1
- package/template/README.md +189 -0
- package/template/api/hello.js +14 -0
- package/template/api/users.js +62 -0
- package/template/app.config.js +41 -0
- package/template/app.html +127 -24
- package/template/components/Counter.jsx +30 -12
- package/template/components/Footer.jsx +3 -3
- package/template/components/Navbar.jsx +16 -0
- package/template/docs/API-GUIDE.md +196 -0
- package/template/docs/AUTO-IMPORT.md +99 -0
- package/template/docs/GETTING-STARTED.md +205 -0
- package/template/docs/QUICK-START.md +111 -0
- package/template/modules/README.md +129 -0
- package/template/modules/api.js +148 -0
- package/template/modules/auto-import.js +130 -0
- package/template/modules/router.js +185 -0
- package/template/modules/state.js +54 -0
- package/template/package.json +18 -0
- package/template/pages/About.jsx +33 -0
- package/template/pages/ApiDemo.jsx +209 -0
- package/template/pages/Contact.jsx +27 -0
- package/template/pages/Home.jsx +39 -0
- package/template/pages/NewPage.jsx +39 -0
- package/template/pages/Test.jsx +74 -0
- package/template/scripts/build.js +81 -0
- package/template/scripts/dev-server.js +172 -0
- package/template/scripts/generate-imports.js +98 -0
- package/template/app.js +0 -83
- package/template/components/Header.jsx +0 -10
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "api-demo" ā "#/api-demo"
|
|
4
|
+
registerPage("api-demo", ApiDemoPage);
|
|
5
|
+
|
|
6
|
+
function ApiDemoPage() {
|
|
7
|
+
const [users, setUsers] = useState([]);
|
|
8
|
+
const [loading, setLoading] = useState(false);
|
|
9
|
+
const [message, setMessage] = useState('');
|
|
10
|
+
const [newUserName, setNewUserName] = useState('');
|
|
11
|
+
const [newUserEmail, setNewUserEmail] = useState('');
|
|
12
|
+
|
|
13
|
+
// Fetch users from API
|
|
14
|
+
const fetchUsers = async () => {
|
|
15
|
+
setLoading(true);
|
|
16
|
+
try {
|
|
17
|
+
const response = await fetchApi('/api/users');
|
|
18
|
+
const data = await response.json();
|
|
19
|
+
setUsers(data);
|
|
20
|
+
setMessage('Users loaded successfully!');
|
|
21
|
+
} catch (error) {
|
|
22
|
+
setMessage('Error loading users: ' + error.message);
|
|
23
|
+
}
|
|
24
|
+
setLoading(false);
|
|
25
|
+
};
|
|
26
|
+
|
|
27
|
+
// Test hello API
|
|
28
|
+
const testHelloApi = async () => {
|
|
29
|
+
try {
|
|
30
|
+
const response = await fetchApi('/api/hello?name=Aziz');
|
|
31
|
+
const data = await response.json();
|
|
32
|
+
setMessage(data.message);
|
|
33
|
+
} catch (error) {
|
|
34
|
+
setMessage('Error: ' + error.message);
|
|
35
|
+
}
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
// Add new user
|
|
39
|
+
const addUser = async (e) => {
|
|
40
|
+
e.preventDefault();
|
|
41
|
+
if (!newUserName || !newUserEmail) {
|
|
42
|
+
setMessage('Please fill in all fields');
|
|
43
|
+
return;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
setLoading(true);
|
|
47
|
+
try {
|
|
48
|
+
const response = await fetchApi('/api/users', {
|
|
49
|
+
method: 'POST',
|
|
50
|
+
body: JSON.stringify({
|
|
51
|
+
name: newUserName,
|
|
52
|
+
email: newUserEmail
|
|
53
|
+
})
|
|
54
|
+
});
|
|
55
|
+
const data = await response.json();
|
|
56
|
+
setMessage(`User added: ${data.name}`);
|
|
57
|
+
setNewUserName('');
|
|
58
|
+
setNewUserEmail('');
|
|
59
|
+
// Refresh users list
|
|
60
|
+
fetchUsers();
|
|
61
|
+
} catch (error) {
|
|
62
|
+
setMessage('Error adding user: ' + error.message);
|
|
63
|
+
}
|
|
64
|
+
setLoading(false);
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// Delete user
|
|
68
|
+
const deleteUser = async (userId) => {
|
|
69
|
+
setLoading(true);
|
|
70
|
+
try {
|
|
71
|
+
const response = await fetchApi(`/api/users?id=${userId}`, {
|
|
72
|
+
method: 'DELETE'
|
|
73
|
+
});
|
|
74
|
+
const data = await response.json();
|
|
75
|
+
setMessage(data.message);
|
|
76
|
+
// Refresh users list
|
|
77
|
+
fetchUsers();
|
|
78
|
+
} catch (error) {
|
|
79
|
+
setMessage('Error deleting user: ' + error.message);
|
|
80
|
+
}
|
|
81
|
+
setLoading(false);
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
return (
|
|
85
|
+
<div>
|
|
86
|
+
<h1>API Routes Demo š</h1>
|
|
87
|
+
|
|
88
|
+
<div class="card">
|
|
89
|
+
<h2>Test API Endpoints</h2>
|
|
90
|
+
<p>This demonstrates the Next.js-like API route system.</p>
|
|
91
|
+
|
|
92
|
+
<div style={{ display: 'flex', gap: '12px', marginBottom: '16px' }}>
|
|
93
|
+
<button class="btn btn-primary" onClick={testHelloApi}>
|
|
94
|
+
Test /api/hello
|
|
95
|
+
</button>
|
|
96
|
+
<button class="btn btn-primary" onClick={fetchUsers}>
|
|
97
|
+
Fetch Users
|
|
98
|
+
</button>
|
|
99
|
+
</div>
|
|
100
|
+
|
|
101
|
+
{message && (
|
|
102
|
+
<div style={{
|
|
103
|
+
padding: '12px',
|
|
104
|
+
background: '#e3f2fd',
|
|
105
|
+
borderRadius: '8px',
|
|
106
|
+
marginTop: '12px',
|
|
107
|
+
color: '#1976d2'
|
|
108
|
+
}}>
|
|
109
|
+
{message}
|
|
110
|
+
</div>
|
|
111
|
+
)}
|
|
112
|
+
</div>
|
|
113
|
+
|
|
114
|
+
<div class="card">
|
|
115
|
+
<h2>Add New User</h2>
|
|
116
|
+
<form onSubmit={addUser}>
|
|
117
|
+
<div>
|
|
118
|
+
<label>Name:</label>
|
|
119
|
+
<input
|
|
120
|
+
type="text"
|
|
121
|
+
value={newUserName}
|
|
122
|
+
onInput={(e) => setNewUserName(e.target.value)}
|
|
123
|
+
placeholder="Enter name"
|
|
124
|
+
/>
|
|
125
|
+
</div>
|
|
126
|
+
|
|
127
|
+
<div>
|
|
128
|
+
<label>Email:</label>
|
|
129
|
+
<input
|
|
130
|
+
type="email"
|
|
131
|
+
value={newUserEmail}
|
|
132
|
+
onInput={(e) => setNewUserEmail(e.target.value)}
|
|
133
|
+
placeholder="Enter email"
|
|
134
|
+
/>
|
|
135
|
+
</div>
|
|
136
|
+
|
|
137
|
+
<button type="submit" class="btn btn-primary" disabled={loading}>
|
|
138
|
+
{loading ? 'Adding...' : 'Add User'}
|
|
139
|
+
</button>
|
|
140
|
+
</form>
|
|
141
|
+
</div>
|
|
142
|
+
|
|
143
|
+
<div class="card">
|
|
144
|
+
<h2>Users List</h2>
|
|
145
|
+
{loading && <p>Loading...</p>}
|
|
146
|
+
|
|
147
|
+
{users.length === 0 && !loading && (
|
|
148
|
+
<p>No users loaded. Click "Fetch Users" to load data.</p>
|
|
149
|
+
)}
|
|
150
|
+
|
|
151
|
+
{users.length > 0 && (
|
|
152
|
+
<div>
|
|
153
|
+
{users.map(user => (
|
|
154
|
+
<div style={{
|
|
155
|
+
padding: '12px',
|
|
156
|
+
background: '#f5f5f5',
|
|
157
|
+
borderRadius: '8px',
|
|
158
|
+
marginBottom: '8px',
|
|
159
|
+
display: 'flex',
|
|
160
|
+
justifyContent: 'space-between',
|
|
161
|
+
alignItems: 'center'
|
|
162
|
+
}}>
|
|
163
|
+
<div>
|
|
164
|
+
<strong>{user.name}</strong>
|
|
165
|
+
<br />
|
|
166
|
+
<span style={{ fontSize: '14px', color: '#666' }}>
|
|
167
|
+
{user.email}
|
|
168
|
+
</span>
|
|
169
|
+
</div>
|
|
170
|
+
<button
|
|
171
|
+
class="btn btn-danger"
|
|
172
|
+
onClick={() => deleteUser(user.id)}
|
|
173
|
+
disabled={loading}
|
|
174
|
+
>
|
|
175
|
+
Delete
|
|
176
|
+
</button>
|
|
177
|
+
</div>
|
|
178
|
+
))}
|
|
179
|
+
</div>
|
|
180
|
+
)}
|
|
181
|
+
</div>
|
|
182
|
+
|
|
183
|
+
<div class="card">
|
|
184
|
+
<h2>How to Use API Routes</h2>
|
|
185
|
+
<p>Create API routes in the <code>api/</code> folder:</p>
|
|
186
|
+
<pre style={{
|
|
187
|
+
background: '#282c34',
|
|
188
|
+
color: '#61dafb',
|
|
189
|
+
padding: '16px',
|
|
190
|
+
borderRadius: '8px',
|
|
191
|
+
overflow: 'auto'
|
|
192
|
+
}}>
|
|
193
|
+
{`// api/myroute.js
|
|
194
|
+
registerApiRoute('myroute', async (req, res) => {
|
|
195
|
+
if (req.method === 'GET') {
|
|
196
|
+
return res.status(200).json({
|
|
197
|
+
message: 'Hello!'
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
});
|
|
201
|
+
|
|
202
|
+
// Use in components
|
|
203
|
+
const response = await fetchApi('/api/myroute');
|
|
204
|
+
const data = await response.json();`}
|
|
205
|
+
</pre>
|
|
206
|
+
</div>
|
|
207
|
+
</div>
|
|
208
|
+
);
|
|
209
|
+
}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "contact" ā "#/contact"
|
|
4
|
+
registerPage("contact", ContactPage);
|
|
5
|
+
|
|
6
|
+
function ContactPage() {
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1>Contact š¬</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>Get in touch</h2>
|
|
13
|
+
<p>Fill out the form below and we'll get back to you.</p>
|
|
14
|
+
|
|
15
|
+
<form onSubmit={(e) => {
|
|
16
|
+
e.preventDefault();
|
|
17
|
+
alert("Message sent! (wire this up to a backend)");
|
|
18
|
+
}}>
|
|
19
|
+
<input type="text" placeholder="Your name" required />
|
|
20
|
+
<input type="email" placeholder="Your email" required />
|
|
21
|
+
<textarea placeholder="Your message" required></textarea>
|
|
22
|
+
<button type="submit" class="btn btn-primary">Send Message</button>
|
|
23
|
+
</form>
|
|
24
|
+
</div>
|
|
25
|
+
</div>
|
|
26
|
+
);
|
|
27
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "home" ā "#/"
|
|
4
|
+
registerPage("home", HomePage);
|
|
5
|
+
|
|
6
|
+
function HomePage() {
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1>Welcome Home š</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>Global State Counter</h2>
|
|
13
|
+
<p>This uses the global state. Click the buttons to update it.</p>
|
|
14
|
+
|
|
15
|
+
<div class="count-display">{state.count}</div>
|
|
16
|
+
|
|
17
|
+
<div class="counter-actions">
|
|
18
|
+
<button class="btn btn-danger" onClick={() => setState({ count: state.count - 1 })}>ā</button>
|
|
19
|
+
<button class="btn btn-primary" onClick={() => setState({ count: state.count + 1 })}>+</button>
|
|
20
|
+
<button class="btn btn-grey" onClick={() => setState({ count: 0 })}>Reset</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h2>File-based Routing</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Each file in <strong>pages/</strong> is a route ā just like Next.js.
|
|
28
|
+
Add a new file, register it in app.js, and it's a new page.
|
|
29
|
+
</p>
|
|
30
|
+
<button class="btn btn-primary" onClick={() => navigate("test")}>
|
|
31
|
+
Go to Test Page ā
|
|
32
|
+
</button>
|
|
33
|
+
<button class="btn btn-primary" onClick={() => navigate("about")}>
|
|
34
|
+
Go to About ā
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "home" ā "#/"
|
|
4
|
+
registerPage("NewPage", NewPage);
|
|
5
|
+
|
|
6
|
+
function NewPage() {
|
|
7
|
+
return (
|
|
8
|
+
<div>
|
|
9
|
+
<h1>Welcome Home š</h1>
|
|
10
|
+
|
|
11
|
+
<div class="card">
|
|
12
|
+
<h2>Global State Counter</h2>
|
|
13
|
+
<p>This uses the global state. Click the buttons to update it.</p>
|
|
14
|
+
|
|
15
|
+
<div class="count-display">{state.count}</div>
|
|
16
|
+
|
|
17
|
+
<div class="counter-actions">
|
|
18
|
+
<button class="btn btn-danger" onClick={() => setState({ count: state.count - 1 })}>ā</button>
|
|
19
|
+
<button class="btn btn-primary" onClick={() => setState({ count: state.count + 1 })}>+</button>
|
|
20
|
+
<button class="btn btn-grey" onClick={() => setState({ count: 0 })}>Reset</button>
|
|
21
|
+
</div>
|
|
22
|
+
</div>
|
|
23
|
+
|
|
24
|
+
<div class="card">
|
|
25
|
+
<h2>File-based Routing</h2>
|
|
26
|
+
<p>
|
|
27
|
+
Each file in <strong>pages/</strong> is a route ā just like Next.js.
|
|
28
|
+
Add a new file, register it in app.js, and it's a new page.
|
|
29
|
+
</p>
|
|
30
|
+
<button class="btn btn-primary" onClick={() => navigate("test")}>
|
|
31
|
+
Go to Test Page ā
|
|
32
|
+
</button>
|
|
33
|
+
<button class="btn btn-primary" onClick={() => navigate("about")}>
|
|
34
|
+
Go to About ā
|
|
35
|
+
</button>
|
|
36
|
+
</div>
|
|
37
|
+
</div>
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
/** @jsx h */
|
|
2
|
+
|
|
3
|
+
// Register this page at route "test" ā "#/test"
|
|
4
|
+
registerPage("test", TestPage);
|
|
5
|
+
|
|
6
|
+
function TestPage() {
|
|
7
|
+
const [count, setCount] = useState(0);
|
|
8
|
+
const [name, setName] = useState('');
|
|
9
|
+
const [email, setEmail] = useState('');
|
|
10
|
+
|
|
11
|
+
const handleSubmit = (e) => {
|
|
12
|
+
e.preventDefault();
|
|
13
|
+
alert(`Form submitted!\nName: ${name}\nEmail: ${email}\nCount: ${count}`);
|
|
14
|
+
};
|
|
15
|
+
|
|
16
|
+
return (
|
|
17
|
+
<div>
|
|
18
|
+
<h1>Test Page š§Ŗ</h1>
|
|
19
|
+
|
|
20
|
+
<div class="card">
|
|
21
|
+
<h2>Counter Example</h2>
|
|
22
|
+
<p>This demonstrates the new state management system.</p>
|
|
23
|
+
|
|
24
|
+
<div class="count-display">{count}</div>
|
|
25
|
+
<div class="counter-actions">
|
|
26
|
+
<button class="btn btn-primary" onClick={() => setCount(count + 1)}>
|
|
27
|
+
Increment
|
|
28
|
+
</button>
|
|
29
|
+
<button class="btn btn-danger" onClick={() => setCount(0)}>
|
|
30
|
+
Reset
|
|
31
|
+
</button>
|
|
32
|
+
</div>
|
|
33
|
+
</div>
|
|
34
|
+
|
|
35
|
+
<div class="card">
|
|
36
|
+
<h2>Form Example</h2>
|
|
37
|
+
<p>Try the form with state management.</p>
|
|
38
|
+
|
|
39
|
+
<form onSubmit={handleSubmit}>
|
|
40
|
+
<div>
|
|
41
|
+
<label>Email:</label>
|
|
42
|
+
<input
|
|
43
|
+
type="email"
|
|
44
|
+
value={email}
|
|
45
|
+
onInput={(e) => setEmail(e.target.value)}
|
|
46
|
+
placeholder="Enter your email"
|
|
47
|
+
/>
|
|
48
|
+
</div>
|
|
49
|
+
<div>
|
|
50
|
+
<label>Name:</label>
|
|
51
|
+
<input
|
|
52
|
+
type="text"
|
|
53
|
+
value={name}
|
|
54
|
+
onInput={(e) => setName(e.target.value)}
|
|
55
|
+
placeholder="Enter your name"
|
|
56
|
+
/>
|
|
57
|
+
</div>
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
<button type="submit" class="btn btn-primary">
|
|
61
|
+
Submit Form
|
|
62
|
+
</button>
|
|
63
|
+
</form>
|
|
64
|
+
</div>
|
|
65
|
+
|
|
66
|
+
<div class="card">
|
|
67
|
+
<h2>State Information</h2>
|
|
68
|
+
<p>Current count: <strong>{count}</strong></p>
|
|
69
|
+
<p>Current name: <strong>{name || 'Not set'}</strong></p>
|
|
70
|
+
<p>Current email: <strong>{email || 'Not set'}</strong></p>
|
|
71
|
+
</div>
|
|
72
|
+
</div>
|
|
73
|
+
);
|
|
74
|
+
}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
|
|
6
|
+
console.log('š Scanning for pages, components, and API routes...\n');
|
|
7
|
+
|
|
8
|
+
// Scan a directory for files
|
|
9
|
+
function scanDirectory(dir, extensions = []) {
|
|
10
|
+
const files = [];
|
|
11
|
+
|
|
12
|
+
if (!fs.existsSync(dir)) {
|
|
13
|
+
return files;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
const items = fs.readdirSync(dir);
|
|
17
|
+
|
|
18
|
+
for (const item of items) {
|
|
19
|
+
const fullPath = path.join(dir, item);
|
|
20
|
+
const stat = fs.statSync(fullPath);
|
|
21
|
+
|
|
22
|
+
if (stat.isFile()) {
|
|
23
|
+
const ext = path.extname(item);
|
|
24
|
+
if (extensions.length === 0 || extensions.includes(ext)) {
|
|
25
|
+
files.push(path.relative('.', fullPath).replace(/\\/g, '/'));
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
return files;
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Scan for files
|
|
34
|
+
const apiRoutes = scanDirectory('api', ['.js']);
|
|
35
|
+
const components = scanDirectory('components', ['.jsx', '.js']);
|
|
36
|
+
const pages = scanDirectory('pages', ['.jsx', '.js']);
|
|
37
|
+
|
|
38
|
+
console.log(`š” Found ${apiRoutes.length} API routes`);
|
|
39
|
+
console.log(`š§© Found ${components.length} components`);
|
|
40
|
+
console.log(`š Found ${pages.length} pages\n`);
|
|
41
|
+
|
|
42
|
+
// Generate app.config.js
|
|
43
|
+
const configContent = `// ============================================================
|
|
44
|
+
// APP CONFIGURATION (AUTO-GENERATED)
|
|
45
|
+
// This file is automatically generated by build.js
|
|
46
|
+
// Run: node build.js to regenerate
|
|
47
|
+
// ============================================================
|
|
48
|
+
|
|
49
|
+
window.APP_CONFIG = {
|
|
50
|
+
apiRoutes: ${JSON.stringify(apiRoutes, null, 8).replace(/\n/g, '\n ')},
|
|
51
|
+
|
|
52
|
+
components: ${JSON.stringify(components, null, 8).replace(/\n/g, '\n ')},
|
|
53
|
+
|
|
54
|
+
pages: ${JSON.stringify(pages, null, 8).replace(/\n/g, '\n ')}
|
|
55
|
+
};
|
|
56
|
+
`;
|
|
57
|
+
|
|
58
|
+
fs.writeFileSync('app.config.js', configContent);
|
|
59
|
+
console.log('ā
Generated app.config.js\n');
|
|
60
|
+
|
|
61
|
+
// Generate script tags for app.html
|
|
62
|
+
const scriptTags = [];
|
|
63
|
+
|
|
64
|
+
// Add API routes
|
|
65
|
+
apiRoutes.forEach(route => {
|
|
66
|
+
scriptTags.push(` <script type="text/babel" src="${route}"></script>`);
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Add components
|
|
70
|
+
components.forEach(component => {
|
|
71
|
+
scriptTags.push(` <script type="text/babel" src="${component}"></script>`);
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
// Add pages
|
|
75
|
+
pages.forEach(page => {
|
|
76
|
+
scriptTags.push(` <script type="text/babel" src="${page}"></script>`);
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
console.log('š Copy these script tags to app.html:\n');
|
|
80
|
+
console.log(scriptTags.join('\n'));
|
|
81
|
+
console.log('\n⨠Done! Refresh your browser to see changes.');
|
|
@@ -0,0 +1,172 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const http = require('http');
|
|
6
|
+
const { exec } = require('child_process');
|
|
7
|
+
|
|
8
|
+
const PORT = process.env.PORT || 3000;
|
|
9
|
+
const clients = []; // Store SSE clients for live reload
|
|
10
|
+
|
|
11
|
+
console.log('š Dev server starting...\n');
|
|
12
|
+
|
|
13
|
+
// Function to generate imports
|
|
14
|
+
function generateImports() {
|
|
15
|
+
console.log('š Regenerating imports...');
|
|
16
|
+
exec('node scripts/generate-imports.js', (error, stdout, stderr) => {
|
|
17
|
+
if (error) {
|
|
18
|
+
console.error('ā Error:', error);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
console.log(stdout);
|
|
22
|
+
// Notify all connected clients to reload
|
|
23
|
+
notifyClients();
|
|
24
|
+
});
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
// Notify all clients to reload
|
|
28
|
+
function notifyClients() {
|
|
29
|
+
console.log('š Notifying browser to reload...');
|
|
30
|
+
clients.forEach(client => {
|
|
31
|
+
client.write('data: reload\n\n');
|
|
32
|
+
});
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// Live reload script to inject
|
|
36
|
+
const liveReloadScript = `
|
|
37
|
+
<script>
|
|
38
|
+
// Live reload client
|
|
39
|
+
const evtSource = new EventSource('/live-reload');
|
|
40
|
+
evtSource.onmessage = (event) => {
|
|
41
|
+
if (event.data === 'reload') {
|
|
42
|
+
console.log('š Changes detected, reloading...');
|
|
43
|
+
location.reload();
|
|
44
|
+
}
|
|
45
|
+
};
|
|
46
|
+
evtSource.onerror = () => {
|
|
47
|
+
console.log('ā ļø Live reload disconnected');
|
|
48
|
+
};
|
|
49
|
+
console.log('ā
Live reload connected');
|
|
50
|
+
</script>
|
|
51
|
+
`;
|
|
52
|
+
|
|
53
|
+
// Create HTTP server
|
|
54
|
+
function createServer(port) {
|
|
55
|
+
return http.createServer((req, res) => {
|
|
56
|
+
// Handle live reload SSE endpoint
|
|
57
|
+
if (req.url === '/live-reload') {
|
|
58
|
+
res.writeHead(200, {
|
|
59
|
+
'Content-Type': 'text/event-stream',
|
|
60
|
+
'Cache-Control': 'no-cache',
|
|
61
|
+
'Connection': 'keep-alive',
|
|
62
|
+
'Access-Control-Allow-Origin': '*'
|
|
63
|
+
});
|
|
64
|
+
|
|
65
|
+
clients.push(res);
|
|
66
|
+
console.log('š Browser connected for live reload');
|
|
67
|
+
|
|
68
|
+
req.on('close', () => {
|
|
69
|
+
const index = clients.indexOf(res);
|
|
70
|
+
if (index !== -1) {
|
|
71
|
+
clients.splice(index, 1);
|
|
72
|
+
}
|
|
73
|
+
console.log('š Browser disconnected from live reload');
|
|
74
|
+
});
|
|
75
|
+
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let filePath = '.' + req.url;
|
|
80
|
+
if (filePath === './') {
|
|
81
|
+
filePath = './app.html';
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
const extname = String(path.extname(filePath)).toLowerCase();
|
|
85
|
+
const mimeTypes = {
|
|
86
|
+
'.html': 'text/html',
|
|
87
|
+
'.js': 'text/javascript',
|
|
88
|
+
'.jsx': 'text/javascript',
|
|
89
|
+
'.css': 'text/css',
|
|
90
|
+
'.json': 'application/json',
|
|
91
|
+
'.png': 'image/png',
|
|
92
|
+
'.jpg': 'image/jpg',
|
|
93
|
+
'.gif': 'image/gif',
|
|
94
|
+
'.svg': 'image/svg+xml',
|
|
95
|
+
'.wav': 'audio/wav',
|
|
96
|
+
'.mp4': 'video/mp4',
|
|
97
|
+
'.woff': 'application/font-woff',
|
|
98
|
+
'.ttf': 'application/font-ttf',
|
|
99
|
+
'.eot': 'application/vnd.ms-fontobject',
|
|
100
|
+
'.otf': 'application/font-otf',
|
|
101
|
+
'.wasm': 'application/wasm'
|
|
102
|
+
};
|
|
103
|
+
|
|
104
|
+
const contentType = mimeTypes[extname] || 'application/octet-stream';
|
|
105
|
+
|
|
106
|
+
fs.readFile(filePath, (error, content) => {
|
|
107
|
+
if (error) {
|
|
108
|
+
if (error.code === 'ENOENT') {
|
|
109
|
+
res.writeHead(404, { 'Content-Type': 'text/html' });
|
|
110
|
+
res.end('<h1>404 - File Not Found</h1>', 'utf-8');
|
|
111
|
+
} else {
|
|
112
|
+
res.writeHead(500);
|
|
113
|
+
res.end('Server Error: ' + error.code, 'utf-8');
|
|
114
|
+
}
|
|
115
|
+
} else {
|
|
116
|
+
// Inject live reload script into HTML files
|
|
117
|
+
if (extname === '.html') {
|
|
118
|
+
content = content.toString().replace('</body>', liveReloadScript + '</body>');
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
res.writeHead(200, { 'Content-Type': contentType });
|
|
122
|
+
res.end(content, 'utf-8');
|
|
123
|
+
}
|
|
124
|
+
});
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
// Function to start server on available port
|
|
129
|
+
function startServer(port) {
|
|
130
|
+
const server = createServer(port);
|
|
131
|
+
|
|
132
|
+
server.listen(port, () => {
|
|
133
|
+
console.log(`š Server running at http://localhost:${port}/`);
|
|
134
|
+
console.log(`š± Open http://localhost:${port}/ in your browser\n`);
|
|
135
|
+
}).on('error', (err) => {
|
|
136
|
+
if (err.code === 'EADDRINUSE') {
|
|
137
|
+
console.log(`ā ļø Port ${port} is busy, trying ${port + 1}...`);
|
|
138
|
+
startServer(port + 1);
|
|
139
|
+
} else {
|
|
140
|
+
console.error('ā Server error:', err);
|
|
141
|
+
}
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
// Watch directories
|
|
146
|
+
const watchDirs = ['api', 'components', 'pages', 'modules'];
|
|
147
|
+
|
|
148
|
+
console.log('š Watching for changes in:');
|
|
149
|
+
watchDirs.forEach(dir => {
|
|
150
|
+
if (fs.existsSync(dir)) {
|
|
151
|
+
console.log(` - ${dir}/`);
|
|
152
|
+
fs.watch(dir, { recursive: false }, (eventType, filename) => {
|
|
153
|
+
if (filename && (filename.endsWith('.js') || filename.endsWith('.jsx'))) {
|
|
154
|
+
console.log(`\nš Detected change: ${dir}/${filename}`);
|
|
155
|
+
generateImports();
|
|
156
|
+
}
|
|
157
|
+
});
|
|
158
|
+
}
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
console.log('\nā
Dev server running with live reload!');
|
|
162
|
+
console.log('š” Create/modify files and browser will auto-refresh');
|
|
163
|
+
console.log('ā¹ļø Press Ctrl+C to stop\n');
|
|
164
|
+
|
|
165
|
+
// Start server
|
|
166
|
+
startServer(PORT);
|
|
167
|
+
|
|
168
|
+
// Generate imports on startup
|
|
169
|
+
generateImports();
|
|
170
|
+
|
|
171
|
+
// Keep the process running
|
|
172
|
+
process.stdin.resume();
|