@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.
@@ -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
+ `);
@@ -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">&times;</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
+ `);
@@ -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
- console.log('');
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
- Make sure core-dev is installed:
428
- curl -sSL https://raw.githubusercontent.com/Sitezip/core.sbs/main/install.sh | bash
466
+ Tools are available locally:
467
+ core-gen - Generate components
468
+ core-dev - Start development server
429
469
 
430
- Or start it manually:
431
- cd ${projectName}
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.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",