@core.sbs/create-core-app 1.0.0 → 1.0.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.
- package/bin/core-dev.js +252 -0
- package/bin/core-gen.js +472 -0
- package/bin/create-core-app.js +45 -5
- package/package.json +4 -2
package/bin/core-dev.js
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const http = require('http');
|
|
4
|
+
const fs = require('fs');
|
|
5
|
+
const path = require('path');
|
|
6
|
+
const chokidar = require('chokidar');
|
|
7
|
+
const WebSocket = require('ws');
|
|
8
|
+
|
|
9
|
+
// Source Map Generator (built-in)
|
|
10
|
+
class SourceMapGenerator {
|
|
11
|
+
constructor() {
|
|
12
|
+
this.version = 3;
|
|
13
|
+
this.file = '';
|
|
14
|
+
this.sourceRoot = '';
|
|
15
|
+
this.sources = [];
|
|
16
|
+
this.names = [];
|
|
17
|
+
this.mappings = '';
|
|
18
|
+
this.sourcesContent = [];
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
generateMap(jsFilePath, sourceFilePath) {
|
|
22
|
+
const jsContent = fs.readFileSync(jsFilePath, 'utf8');
|
|
23
|
+
const sourceContent = fs.readFileSync(sourceFilePath, 'utf8');
|
|
24
|
+
|
|
25
|
+
this.file = path.basename(jsFilePath);
|
|
26
|
+
this.sources = [path.basename(sourceFilePath)];
|
|
27
|
+
this.sourcesContent = [sourceContent];
|
|
28
|
+
|
|
29
|
+
// Generate basic mappings (line 1:1 -> 1:1 for each line)
|
|
30
|
+
const lines = jsContent.split('\n');
|
|
31
|
+
const mappings = [];
|
|
32
|
+
|
|
33
|
+
for (let i = 0; i < lines.length; i++) {
|
|
34
|
+
if (i === 0) {
|
|
35
|
+
mappings.push('AAAA');
|
|
36
|
+
} else {
|
|
37
|
+
mappings.push(';AACA');
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
this.mappings = mappings.join('');
|
|
42
|
+
return this.toJSON();
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
toJSON() {
|
|
46
|
+
return {
|
|
47
|
+
version: this.version,
|
|
48
|
+
file: this.file,
|
|
49
|
+
sourceRoot: this.sourceRoot,
|
|
50
|
+
sources: this.sources,
|
|
51
|
+
names: this.names,
|
|
52
|
+
mappings: this.mappings,
|
|
53
|
+
sourcesContent: this.sourcesContent
|
|
54
|
+
};
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// Configuration
|
|
59
|
+
const config = {
|
|
60
|
+
port: process.env.PORT || 3000,
|
|
61
|
+
host: process.env.HOST || 'localhost',
|
|
62
|
+
watchDirs: ['.', 'components'],
|
|
63
|
+
excludePatterns: ['node_modules', '.git', '*.log'],
|
|
64
|
+
openBrowser: process.env.OPEN_BROWSER !== 'false'
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
// MIME types
|
|
68
|
+
const mimeTypes = {
|
|
69
|
+
'.html': 'text/html',
|
|
70
|
+
'.css': 'text/css',
|
|
71
|
+
'.js': 'text/javascript',
|
|
72
|
+
'.json': 'application/json',
|
|
73
|
+
'.png': 'image/png',
|
|
74
|
+
'.jpg': 'image/jpeg',
|
|
75
|
+
'.gif': 'image/gif',
|
|
76
|
+
'.svg': 'image/svg+xml',
|
|
77
|
+
'.ico': 'image/x-icon'
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
// Get MIME type
|
|
81
|
+
function getMimeType(filePath) {
|
|
82
|
+
const ext = path.extname(filePath).toLowerCase();
|
|
83
|
+
return mimeTypes[ext] || 'text/plain';
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Serve file with optional source map
|
|
87
|
+
function serveFile(res, filePath) {
|
|
88
|
+
try {
|
|
89
|
+
const content = fs.readFileSync(filePath);
|
|
90
|
+
const mimeType = getMimeType(filePath);
|
|
91
|
+
|
|
92
|
+
// Generate source map for JS files
|
|
93
|
+
if (mimeType === 'text/javascript') {
|
|
94
|
+
const sourceMapGenerator = new SourceMapGenerator();
|
|
95
|
+
const sourceMap = sourceMapGenerator.generateMap(filePath, filePath);
|
|
96
|
+
|
|
97
|
+
res.setHeader('Content-Type', mimeType);
|
|
98
|
+
res.setHeader('Content-Length', content.length);
|
|
99
|
+
|
|
100
|
+
// Add source map comment
|
|
101
|
+
const contentWithMap = content + `\n//# sourceMappingURL=${path.basename(filePath)}.map`;
|
|
102
|
+
res.end(contentWithMap);
|
|
103
|
+
|
|
104
|
+
// Write source map file
|
|
105
|
+
const mapPath = filePath + '.map';
|
|
106
|
+
fs.writeFileSync(mapPath, JSON.stringify(sourceMap, null, 2));
|
|
107
|
+
} else {
|
|
108
|
+
res.setHeader('Content-Type', mimeType);
|
|
109
|
+
res.setHeader('Content-Length', content.length);
|
|
110
|
+
res.end(content);
|
|
111
|
+
}
|
|
112
|
+
} catch (error) {
|
|
113
|
+
console.error(`Error serving ${filePath}:`, error.message);
|
|
114
|
+
res.statusCode = 404;
|
|
115
|
+
res.end('Not Found');
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Create HTTP server
|
|
120
|
+
const server = http.createServer((req, res) => {
|
|
121
|
+
let filePath = path.join(process.cwd(), req.url === '/' ? 'index.html' : req.url);
|
|
122
|
+
|
|
123
|
+
// Security: prevent directory traversal
|
|
124
|
+
if (filePath.includes('..')) {
|
|
125
|
+
res.statusCode = 400;
|
|
126
|
+
res.end('Bad Request');
|
|
127
|
+
return;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle 404 for missing files
|
|
131
|
+
if (!fs.existsSync(filePath)) {
|
|
132
|
+
// Try to find index.html in directory
|
|
133
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
134
|
+
filePath = path.join(filePath, 'index.html');
|
|
135
|
+
}
|
|
136
|
+
|
|
137
|
+
if (!fs.existsSync(filePath)) {
|
|
138
|
+
res.statusCode = 404;
|
|
139
|
+
res.end('Not Found');
|
|
140
|
+
return;
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
serveFile(res, filePath);
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
// WebSocket server for hot reload
|
|
148
|
+
const wss = new WebSocket.Server({ port: 3001 });
|
|
149
|
+
|
|
150
|
+
// File watcher
|
|
151
|
+
const watcher = chokidar.watch(config.watchDirs, {
|
|
152
|
+
ignored: config.excludePatterns,
|
|
153
|
+
persistent: true
|
|
154
|
+
});
|
|
155
|
+
|
|
156
|
+
watcher.on('change', (filePath) => {
|
|
157
|
+
console.log(`📝 File changed: ${path.relative(process.cwd(), filePath)}`);
|
|
158
|
+
|
|
159
|
+
// Notify all connected clients
|
|
160
|
+
wss.clients.forEach(client => {
|
|
161
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
162
|
+
client.send(JSON.stringify({ type: 'reload', file: filePath }));
|
|
163
|
+
}
|
|
164
|
+
});
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
watcher.on('add', (filePath) => {
|
|
168
|
+
console.log(`➕ File added: ${path.relative(process.cwd(), filePath)}`);
|
|
169
|
+
|
|
170
|
+
// Notify all connected clients
|
|
171
|
+
wss.clients.forEach(client => {
|
|
172
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
173
|
+
client.send(JSON.stringify({ type: 'reload', file: filePath }));
|
|
174
|
+
}
|
|
175
|
+
});
|
|
176
|
+
});
|
|
177
|
+
|
|
178
|
+
watcher.on('unlink', (filePath) => {
|
|
179
|
+
console.log(`➖ File removed: ${path.relative(process.cwd(), filePath)}`);
|
|
180
|
+
|
|
181
|
+
// Notify all connected clients
|
|
182
|
+
wss.clients.forEach(client => {
|
|
183
|
+
if (client.readyState === WebSocket.OPEN) {
|
|
184
|
+
client.send(JSON.stringify({ type: 'reload', file: filePath }));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
});
|
|
188
|
+
|
|
189
|
+
// WebSocket connection handling
|
|
190
|
+
wss.on('connection', (ws) => {
|
|
191
|
+
console.log('🔌 Client connected for hot reload');
|
|
192
|
+
|
|
193
|
+
ws.on('close', () => {
|
|
194
|
+
console.log('🔌 Client disconnected');
|
|
195
|
+
});
|
|
196
|
+
});
|
|
197
|
+
|
|
198
|
+
// Start server
|
|
199
|
+
server.listen(config.port, config.host, () => {
|
|
200
|
+
console.log(`🚀 Development server running at http://${config.host}:${config.port}`);
|
|
201
|
+
console.log(`📁 Watching: ${config.watchDirs.join(', ')}`);
|
|
202
|
+
console.log(`🔌 Hot reload enabled on port 3001`);
|
|
203
|
+
|
|
204
|
+
// Open browser if requested
|
|
205
|
+
if (config.openBrowser) {
|
|
206
|
+
const { spawn } = require('child_process');
|
|
207
|
+
const url = `http://${config.host}:${config.port}`;
|
|
208
|
+
|
|
209
|
+
switch (process.platform) {
|
|
210
|
+
case 'darwin': // macOS
|
|
211
|
+
spawn('open', [url]);
|
|
212
|
+
break;
|
|
213
|
+
case 'win32': // Windows
|
|
214
|
+
spawn('start', [url]);
|
|
215
|
+
break;
|
|
216
|
+
default: // Linux
|
|
217
|
+
spawn('xdg-open', [url]);
|
|
218
|
+
break;
|
|
219
|
+
}
|
|
220
|
+
}
|
|
221
|
+
});
|
|
222
|
+
|
|
223
|
+
// Handle graceful shutdown
|
|
224
|
+
process.on('SIGINT', () => {
|
|
225
|
+
console.log('\n👋 Shutting down development server...');
|
|
226
|
+
server.close();
|
|
227
|
+
wss.close();
|
|
228
|
+
watcher.close();
|
|
229
|
+
process.exit(0);
|
|
230
|
+
});
|
|
231
|
+
|
|
232
|
+
// Handle errors
|
|
233
|
+
server.on('error', (error) => {
|
|
234
|
+
if (error.code === 'EADDRINUSE') {
|
|
235
|
+
console.error(`❌ Port ${config.port} is already in use. Try a different port or close the other process.`);
|
|
236
|
+
} else {
|
|
237
|
+
console.error('❌ Server error:', error);
|
|
238
|
+
}
|
|
239
|
+
process.exit(1);
|
|
240
|
+
});
|
|
241
|
+
|
|
242
|
+
console.log(`
|
|
243
|
+
╔═══════════════════════════════════════╗
|
|
244
|
+
║ ║
|
|
245
|
+
║ core-dev ║
|
|
246
|
+
║ ║
|
|
247
|
+
╚═══════════════════════════════════════╝
|
|
248
|
+
|
|
249
|
+
Development server with hot reload for core.js applications.
|
|
250
|
+
|
|
251
|
+
Starting server...
|
|
252
|
+
`);
|
package/bin/core-gen.js
ADDED
|
@@ -0,0 +1,472 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs');
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const https = require('https');
|
|
6
|
+
|
|
7
|
+
const args = process.argv.slice(2);
|
|
8
|
+
const componentType = args[0];
|
|
9
|
+
const flags = args.slice(1);
|
|
10
|
+
|
|
11
|
+
// Parse flags
|
|
12
|
+
const hasValidation = flags.includes('--validation');
|
|
13
|
+
const hasSortable = flags.includes('--sortable');
|
|
14
|
+
const hasPaginated = flags.includes('--paginated');
|
|
15
|
+
const shouldInject = flags.includes('--inject');
|
|
16
|
+
const nameFlag = flags.find(f => f.startsWith('--name='));
|
|
17
|
+
const injectFileFlag = flags.find(f => f.startsWith('--file='));
|
|
18
|
+
const componentName = nameFlag ? nameFlag.split('=')[1] : 'my';
|
|
19
|
+
const injectFile = injectFileFlag ? injectFileFlag.split('=')[1] : 'index.html';
|
|
20
|
+
|
|
21
|
+
console.log(`
|
|
22
|
+
╔═══════════════════════════════════════╗
|
|
23
|
+
║ ║
|
|
24
|
+
║ core-gen ║
|
|
25
|
+
║ ║
|
|
26
|
+
╚═══════════════════════════════════════╝
|
|
27
|
+
`);
|
|
28
|
+
|
|
29
|
+
if (!componentType || componentType === '-h' || componentType === '--help') {
|
|
30
|
+
console.log(`
|
|
31
|
+
Usage: core-gen <component> [options]
|
|
32
|
+
|
|
33
|
+
Components:
|
|
34
|
+
form Generate a form component
|
|
35
|
+
table Generate a table component
|
|
36
|
+
modal Generate a modal component
|
|
37
|
+
card Generate a card component
|
|
38
|
+
alert Generate an alert component
|
|
39
|
+
navbar Generate a navigation bar
|
|
40
|
+
badge Generate a badge component
|
|
41
|
+
button Generate a button component
|
|
42
|
+
breadcrumb Generate a breadcrumb component
|
|
43
|
+
tabs Generate a tabs component
|
|
44
|
+
accordion Generate an accordion component
|
|
45
|
+
pagination Generate a pagination component
|
|
46
|
+
spinner Generate a loading spinner
|
|
47
|
+
avatar Generate an avatar component
|
|
48
|
+
progressbar Generate a progress bar
|
|
49
|
+
dropdown Generate a dropdown menu
|
|
50
|
+
toast Generate a toast notification
|
|
51
|
+
|
|
52
|
+
Options:
|
|
53
|
+
--validation Add form validation (form only)
|
|
54
|
+
--sortable Add sorting (table only)
|
|
55
|
+
--paginated Add pagination (table only)
|
|
56
|
+
--inject Inject into HTML file
|
|
57
|
+
--name=<name> Component name
|
|
58
|
+
--file=<file> Target HTML file (with --inject)
|
|
59
|
+
|
|
60
|
+
Examples:
|
|
61
|
+
core-gen form --validation --name=contact
|
|
62
|
+
core-gen table --sortable --paginated --name=users
|
|
63
|
+
core-gen modal --name=confirm --inject --file=index.html
|
|
64
|
+
`);
|
|
65
|
+
process.exit(0);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// Component templates
|
|
69
|
+
const templates = {
|
|
70
|
+
form: {
|
|
71
|
+
html: `<!-- Form Component -->
|
|
72
|
+
<div class="form-container" id="${componentName}-form">
|
|
73
|
+
<form class="core-form" id="${componentName}-form-element">
|
|
74
|
+
<div class="form-group">
|
|
75
|
+
<label for="${componentName}-name">Name</label>
|
|
76
|
+
<input type="text" id="${componentName}-name" name="name" required>
|
|
77
|
+
</div>
|
|
78
|
+
<div class="form-group">
|
|
79
|
+
<label for="${componentName}-email">Email</label>
|
|
80
|
+
<input type="email" id="${componentName}-email" name="email" required>
|
|
81
|
+
</div>
|
|
82
|
+
<div class="form-group">
|
|
83
|
+
<label for="${componentName}-message">Message</label>
|
|
84
|
+
<textarea id="${componentName}-message" name="message" rows="4"></textarea>
|
|
85
|
+
</div>
|
|
86
|
+
<button type="submit" class="btn btn-primary">Submit</button>
|
|
87
|
+
</form>
|
|
88
|
+
</div>`,
|
|
89
|
+
css: `/* Form Component Styles */
|
|
90
|
+
.form-container {
|
|
91
|
+
max-width: 500px;
|
|
92
|
+
margin: 2rem auto;
|
|
93
|
+
padding: 2rem;
|
|
94
|
+
background: white;
|
|
95
|
+
border-radius: 8px;
|
|
96
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
.core-form .form-group {
|
|
100
|
+
margin-bottom: 1.5rem;
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
.core-form label {
|
|
104
|
+
display: block;
|
|
105
|
+
margin-bottom: 0.5rem;
|
|
106
|
+
font-weight: 600;
|
|
107
|
+
color: #333;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
.core-form input,
|
|
111
|
+
.core-form textarea {
|
|
112
|
+
width: 100%;
|
|
113
|
+
padding: 0.75rem;
|
|
114
|
+
border: 1px solid #ddd;
|
|
115
|
+
border-radius: 4px;
|
|
116
|
+
font-size: 1rem;
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
.core-form input:focus,
|
|
120
|
+
.core-form textarea:focus {
|
|
121
|
+
outline: none;
|
|
122
|
+
border-color: #007bff;
|
|
123
|
+
box-shadow: 0 0 0 2px rgba(0,123,255,0.25);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
.btn {
|
|
127
|
+
padding: 0.75rem 1.5rem;
|
|
128
|
+
border: none;
|
|
129
|
+
border-radius: 4px;
|
|
130
|
+
font-size: 1rem;
|
|
131
|
+
cursor: pointer;
|
|
132
|
+
transition: all 0.2s;
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
.btn-primary {
|
|
136
|
+
background: #007bff;
|
|
137
|
+
color: white;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.btn-primary:hover {
|
|
141
|
+
background: #0056b3;
|
|
142
|
+
}`,
|
|
143
|
+
js: hasValidation ? `// Form validation
|
|
144
|
+
document.getElementById('${componentName}-form-element').addEventListener('submit', function(e) {
|
|
145
|
+
e.preventDefault();
|
|
146
|
+
|
|
147
|
+
const name = document.getElementById('${componentName}-name').value;
|
|
148
|
+
const email = document.getElementById('${componentName}-email').value;
|
|
149
|
+
const message = document.getElementById('${componentName}-message').value;
|
|
150
|
+
|
|
151
|
+
// Basic validation
|
|
152
|
+
if (!name || !email || !message) {
|
|
153
|
+
alert('Please fill in all fields');
|
|
154
|
+
return;
|
|
155
|
+
}
|
|
156
|
+
|
|
157
|
+
if (!email.includes('@')) {
|
|
158
|
+
alert('Please enter a valid email');
|
|
159
|
+
return;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
console.log('Form submitted:', { name, email, message });
|
|
163
|
+
alert('Form submitted successfully!');
|
|
164
|
+
});` : ''
|
|
165
|
+
},
|
|
166
|
+
|
|
167
|
+
table: {
|
|
168
|
+
html: `<!-- Table Component -->
|
|
169
|
+
<div class="table-container" id="${componentName}-table">
|
|
170
|
+
<table class="data-table" id="${componentName}-table-element">
|
|
171
|
+
<thead>
|
|
172
|
+
<tr>
|
|
173
|
+
<th>Name</th>
|
|
174
|
+
<th>Email</th>
|
|
175
|
+
<th>Role</th>
|
|
176
|
+
<th>Status</th>
|
|
177
|
+
</tr>
|
|
178
|
+
</thead>
|
|
179
|
+
<tbody>
|
|
180
|
+
<tr>
|
|
181
|
+
<td>John Doe</td>
|
|
182
|
+
<td>john@example.com</td>
|
|
183
|
+
<td>Admin</td>
|
|
184
|
+
<td>Active</td>
|
|
185
|
+
</tr>
|
|
186
|
+
<tr>
|
|
187
|
+
<td>Jane Smith</td>
|
|
188
|
+
<td>jane@example.com</td>
|
|
189
|
+
<td>User</td>
|
|
190
|
+
<td>Active</td>
|
|
191
|
+
</tr>
|
|
192
|
+
<tr>
|
|
193
|
+
<td>Bob Johnson</td>
|
|
194
|
+
<td>bob@example.com</td>
|
|
195
|
+
<td>User</td>
|
|
196
|
+
<td>Inactive</td>
|
|
197
|
+
</tr>
|
|
198
|
+
</tbody>
|
|
199
|
+
</table>
|
|
200
|
+
</div>`,
|
|
201
|
+
css: `/* Table Component Styles */
|
|
202
|
+
.table-container {
|
|
203
|
+
padding: 2rem;
|
|
204
|
+
background: white;
|
|
205
|
+
border-radius: 8px;
|
|
206
|
+
box-shadow: 0 2px 10px rgba(0,0,0,0.1);
|
|
207
|
+
overflow-x: auto;
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
.data-table {
|
|
211
|
+
width: 100%;
|
|
212
|
+
border-collapse: collapse;
|
|
213
|
+
margin-top: 1rem;
|
|
214
|
+
}
|
|
215
|
+
|
|
216
|
+
.data-table th,
|
|
217
|
+
.data-table td {
|
|
218
|
+
padding: 0.75rem;
|
|
219
|
+
text-align: left;
|
|
220
|
+
border-bottom: 1px solid #ddd;
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
.data-table th {
|
|
224
|
+
background: #f8f9fa;
|
|
225
|
+
font-weight: 600;
|
|
226
|
+
color: #495057;
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
.data-table tbody tr:hover {
|
|
230
|
+
background: #f8f9fa;
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
.data-table td:nth-child(4) {
|
|
234
|
+
font-weight: 500;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
.data-table td:nth-child(4):contains('Active') {
|
|
238
|
+
color: #28a745;
|
|
239
|
+
}
|
|
240
|
+
|
|
241
|
+
.data-table td:nth-child(4):contains('Inactive') {
|
|
242
|
+
color: #dc3545;
|
|
243
|
+
}`,
|
|
244
|
+
js: hasSortable ? `// Table sorting
|
|
245
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
246
|
+
const table = document.getElementById('${componentName}-table-element');
|
|
247
|
+
const headers = table.querySelectorAll('th');
|
|
248
|
+
|
|
249
|
+
headers.forEach((header, index) => {
|
|
250
|
+
header.style.cursor = 'pointer';
|
|
251
|
+
header.addEventListener('click', () => sortTable(index));
|
|
252
|
+
});
|
|
253
|
+
|
|
254
|
+
function sortTable(columnIndex) {
|
|
255
|
+
const tbody = table.querySelector('tbody');
|
|
256
|
+
const rows = Array.from(tbody.querySelectorAll('tr'));
|
|
257
|
+
|
|
258
|
+
rows.sort((a, b) => {
|
|
259
|
+
const aValue = a.cells[columnIndex].textContent;
|
|
260
|
+
const bValue = b.cells[columnIndex].textContent;
|
|
261
|
+
return aValue.localeCompare(bValue);
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
tbody.innerHTML = '';
|
|
265
|
+
rows.forEach(row => tbody.appendChild(row));
|
|
266
|
+
}
|
|
267
|
+
});` : ''
|
|
268
|
+
},
|
|
269
|
+
|
|
270
|
+
modal: {
|
|
271
|
+
html: `<!-- Modal Component -->
|
|
272
|
+
<div class="modal-overlay" id="${componentName}-modal-overlay">
|
|
273
|
+
<div class="modal-container" id="${componentName}-modal">
|
|
274
|
+
<div class="modal-header">
|
|
275
|
+
<h3 class="modal-title">Modal Title</h3>
|
|
276
|
+
<button class="modal-close" id="${componentName}-modal-close">×</button>
|
|
277
|
+
</div>
|
|
278
|
+
<div class="modal-body">
|
|
279
|
+
<p>This is the modal content. You can put anything here.</p>
|
|
280
|
+
</div>
|
|
281
|
+
<div class="modal-footer">
|
|
282
|
+
<button class="btn btn-secondary" id="${componentName}-modal-cancel">Cancel</button>
|
|
283
|
+
<button class="btn btn-primary" id="${componentName}-modal-confirm">Confirm</button>
|
|
284
|
+
</div>
|
|
285
|
+
</div>
|
|
286
|
+
</div>`,
|
|
287
|
+
css: `/* Modal Component Styles */
|
|
288
|
+
.modal-overlay {
|
|
289
|
+
position: fixed;
|
|
290
|
+
top: 0;
|
|
291
|
+
left: 0;
|
|
292
|
+
width: 100%;
|
|
293
|
+
height: 100%;
|
|
294
|
+
background: rgba(0,0,0,0.5);
|
|
295
|
+
display: flex;
|
|
296
|
+
align-items: center;
|
|
297
|
+
justify-content: center;
|
|
298
|
+
z-index: 1000;
|
|
299
|
+
opacity: 0;
|
|
300
|
+
visibility: hidden;
|
|
301
|
+
transition: all 0.3s ease;
|
|
302
|
+
}
|
|
303
|
+
|
|
304
|
+
.modal-overlay.active {
|
|
305
|
+
opacity: 1;
|
|
306
|
+
visibility: visible;
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
.modal-container {
|
|
310
|
+
background: white;
|
|
311
|
+
border-radius: 8px;
|
|
312
|
+
max-width: 500px;
|
|
313
|
+
width: 90%;
|
|
314
|
+
max-height: 90vh;
|
|
315
|
+
overflow-y: auto;
|
|
316
|
+
transform: scale(0.9);
|
|
317
|
+
transition: transform 0.3s ease;
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
.modal-overlay.active .modal-container {
|
|
321
|
+
transform: scale(1);
|
|
322
|
+
}
|
|
323
|
+
|
|
324
|
+
.modal-header {
|
|
325
|
+
padding: 1.5rem;
|
|
326
|
+
border-bottom: 1px solid #eee;
|
|
327
|
+
display: flex;
|
|
328
|
+
justify-content: space-between;
|
|
329
|
+
align-items: center;
|
|
330
|
+
}
|
|
331
|
+
|
|
332
|
+
.modal-title {
|
|
333
|
+
margin: 0;
|
|
334
|
+
font-size: 1.25rem;
|
|
335
|
+
font-weight: 600;
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
.modal-close {
|
|
339
|
+
background: none;
|
|
340
|
+
border: none;
|
|
341
|
+
font-size: 1.5rem;
|
|
342
|
+
cursor: pointer;
|
|
343
|
+
color: #666;
|
|
344
|
+
padding: 0;
|
|
345
|
+
width: 2rem;
|
|
346
|
+
height: 2rem;
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
.modal-body {
|
|
350
|
+
padding: 1.5rem;
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
.modal-footer {
|
|
354
|
+
padding: 1.5rem;
|
|
355
|
+
border-top: 1px solid #eee;
|
|
356
|
+
display: flex;
|
|
357
|
+
justify-content: flex-end;
|
|
358
|
+
gap: 1rem;
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
.btn {
|
|
362
|
+
padding: 0.75rem 1.5rem;
|
|
363
|
+
border: none;
|
|
364
|
+
border-radius: 4px;
|
|
365
|
+
font-size: 1rem;
|
|
366
|
+
cursor: pointer;
|
|
367
|
+
transition: all 0.2s;
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
.btn-primary {
|
|
371
|
+
background: #007bff;
|
|
372
|
+
color: white;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
.btn-secondary {
|
|
376
|
+
background: #6c757d;
|
|
377
|
+
color: white;
|
|
378
|
+
}`,
|
|
379
|
+
js: `// Modal functionality
|
|
380
|
+
document.addEventListener('DOMContentLoaded', function() {
|
|
381
|
+
const overlay = document.getElementById('${componentName}-modal-overlay');
|
|
382
|
+
const closeBtn = document.getElementById('${componentName}-modal-close');
|
|
383
|
+
const cancelBtn = document.getElementById('${componentName}-modal-cancel');
|
|
384
|
+
|
|
385
|
+
function openModal() {
|
|
386
|
+
overlay.classList.add('active');
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
function closeModal() {
|
|
390
|
+
overlay.classList.remove('active');
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
closeBtn.addEventListener('click', closeModal);
|
|
394
|
+
cancelBtn.addEventListener('click', closeModal);
|
|
395
|
+
|
|
396
|
+
overlay.addEventListener('click', function(e) {
|
|
397
|
+
if (e.target === overlay) {
|
|
398
|
+
closeModal();
|
|
399
|
+
}
|
|
400
|
+
});
|
|
401
|
+
|
|
402
|
+
// Make openModal globally accessible
|
|
403
|
+
window.openModal = openModal;
|
|
404
|
+
});`
|
|
405
|
+
}
|
|
406
|
+
};
|
|
407
|
+
|
|
408
|
+
// Generate component
|
|
409
|
+
const template = templates[componentType];
|
|
410
|
+
|
|
411
|
+
if (!template) {
|
|
412
|
+
console.error(`❌ Error: Component "${componentType}" not found.`);
|
|
413
|
+
console.log('Run "core-gen --help" to see available components.');
|
|
414
|
+
process.exit(1);
|
|
415
|
+
}
|
|
416
|
+
|
|
417
|
+
console.log(`🎨 Generating ${componentType} component...`);
|
|
418
|
+
|
|
419
|
+
// Create output directory if it doesn't exist
|
|
420
|
+
const outputDir = path.join(process.cwd(), 'components');
|
|
421
|
+
if (!fs.existsSync(outputDir)) {
|
|
422
|
+
fs.mkdirSync(outputDir, { recursive: true });
|
|
423
|
+
}
|
|
424
|
+
|
|
425
|
+
// Write HTML file
|
|
426
|
+
const htmlPath = path.join(outputDir, `${componentName}.html`);
|
|
427
|
+
fs.writeFileSync(htmlPath, template.html, 'utf8');
|
|
428
|
+
console.log(`✓ Created ${htmlPath}`);
|
|
429
|
+
|
|
430
|
+
// Write CSS file
|
|
431
|
+
const cssPath = path.join(outputDir, `${componentName}.css`);
|
|
432
|
+
fs.writeFileSync(cssPath, template.css, 'utf8');
|
|
433
|
+
console.log(`✓ Created ${cssPath}`);
|
|
434
|
+
|
|
435
|
+
// Write JS file if present
|
|
436
|
+
if (template.js) {
|
|
437
|
+
const jsPath = path.join(outputDir, `${componentName}.js`);
|
|
438
|
+
fs.writeFileSync(jsPath, template.js, 'utf8');
|
|
439
|
+
console.log(`✓ Created ${jsPath}`);
|
|
440
|
+
}
|
|
441
|
+
|
|
442
|
+
// Inject into HTML file if requested
|
|
443
|
+
if (shouldInject) {
|
|
444
|
+
const targetFile = path.join(process.cwd(), injectFile);
|
|
445
|
+
|
|
446
|
+
if (!fs.existsSync(targetFile)) {
|
|
447
|
+
console.error(`❌ Error: Target file "${injectFile}" not found.`);
|
|
448
|
+
process.exit(1);
|
|
449
|
+
}
|
|
450
|
+
|
|
451
|
+
let content = fs.readFileSync(targetFile, 'utf8');
|
|
452
|
+
|
|
453
|
+
// Add CSS link
|
|
454
|
+
const cssLink = ` <link rel="stylesheet" href="components/${componentName}.css">`;
|
|
455
|
+
content = content.replace('</head>', cssLink + '\n</head>');
|
|
456
|
+
|
|
457
|
+
// Add JS script
|
|
458
|
+
const jsScript = ` <script src="components/${componentName}.js"></script>`;
|
|
459
|
+
content = content.replace('</body>', jsScript + '\n</body>');
|
|
460
|
+
|
|
461
|
+
fs.writeFileSync(targetFile, content, 'utf8');
|
|
462
|
+
console.log(`✓ Injected component into ${targetFile}`);
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
console.log(`
|
|
466
|
+
✅ Success! Component generated in components/ folder.
|
|
467
|
+
|
|
468
|
+
Usage:
|
|
469
|
+
- Link CSS: <link rel="stylesheet" href="components/${componentName}.css">
|
|
470
|
+
- Include HTML: Copy from components/${componentName}.html
|
|
471
|
+
- Add JS: <script src="components/${componentName}.js"></script>
|
|
472
|
+
`);
|
package/bin/create-core-app.js
CHANGED
|
@@ -306,6 +306,29 @@ const rl = readline.createInterface({
|
|
|
306
306
|
output: process.stdout
|
|
307
307
|
});
|
|
308
308
|
|
|
309
|
+
// Install tool locally
|
|
310
|
+
function installTool(toolName) {
|
|
311
|
+
const homeDir = process.env.USERPROFILE || process.env.HOME;
|
|
312
|
+
const coreSbsDir = path.join(homeDir, '.core-sbs');
|
|
313
|
+
const toolPath = path.join(coreSbsDir, `${toolName}.js`);
|
|
314
|
+
|
|
315
|
+
// Create .core-sbs directory if it doesn't exist
|
|
316
|
+
if (!fs.existsSync(coreSbsDir)) {
|
|
317
|
+
fs.mkdirSync(coreSbsDir, { recursive: true });
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
// Get the path to the tool from the npm package
|
|
321
|
+
const npmToolPath = path.join(__dirname, `${toolName}.js`);
|
|
322
|
+
|
|
323
|
+
// Copy the tool to the local directory
|
|
324
|
+
if (fs.existsSync(npmToolPath)) {
|
|
325
|
+
fs.copyFileSync(npmToolPath, toolPath);
|
|
326
|
+
console.log(`✅ ${toolName} installed locally`);
|
|
327
|
+
} else {
|
|
328
|
+
console.error(`❌ Failed to install ${toolName}`);
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
|
|
309
332
|
// Promisify question
|
|
310
333
|
function ask(question) {
|
|
311
334
|
return new Promise((resolve) => {
|
|
@@ -340,7 +363,23 @@ async function main() {
|
|
|
340
363
|
|
|
341
364
|
rl.close();
|
|
342
365
|
|
|
343
|
-
|
|
366
|
+
if (wantsDevServer) {
|
|
367
|
+
console.log(`
|
|
368
|
+
📦 Installing development server...
|
|
369
|
+
`);
|
|
370
|
+
|
|
371
|
+
// Install core-dev locally
|
|
372
|
+
installTool('core-dev');
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
if (wantsComponentGen) {
|
|
376
|
+
console.log(`
|
|
377
|
+
📦 Installing component generator...
|
|
378
|
+
`);
|
|
379
|
+
|
|
380
|
+
// Install core-gen locally
|
|
381
|
+
installTool('core-gen');
|
|
382
|
+
}
|
|
344
383
|
|
|
345
384
|
// Create project directory
|
|
346
385
|
const projectPath = path.join(process.cwd(), projectName);
|
|
@@ -424,11 +463,12 @@ Happy coding! 🎉
|
|
|
424
463
|
devServer.on('error', (err) => {
|
|
425
464
|
console.error(`❌ Failed to start dev server: ${err.message}`);
|
|
426
465
|
console.log(`
|
|
427
|
-
|
|
428
|
-
|
|
466
|
+
Tools are available locally:
|
|
467
|
+
core-gen - Generate components
|
|
468
|
+
core-dev - Start development server
|
|
429
469
|
|
|
430
|
-
Or
|
|
431
|
-
|
|
470
|
+
Or run them directly:
|
|
471
|
+
core-gen form --validation
|
|
432
472
|
core-dev
|
|
433
473
|
`);
|
|
434
474
|
});
|
package/package.json
CHANGED
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@core.sbs/create-core-app",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.1",
|
|
4
4
|
"description": "Scaffold a new core.js application. Optionally include a development server, component generator, and more.",
|
|
5
5
|
"bin": {
|
|
6
|
-
"create-core-app": "./bin/create-core-app.js"
|
|
6
|
+
"create-core-app": "./bin/create-core-app.js",
|
|
7
|
+
"core-gen": "./bin/core-gen.js",
|
|
8
|
+
"core-dev": "./bin/core-dev.js"
|
|
7
9
|
},
|
|
8
10
|
"keywords": [
|
|
9
11
|
"core.js",
|