create-nativecore 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (175) hide show
  1. package/README.md +10 -18
  2. package/bin/index.mjs +407 -489
  3. package/package.json +4 -3
  4. package/template/.env.example +28 -0
  5. package/template/.htmlhintrc +14 -0
  6. package/template/api/data/dashboard.json +11 -0
  7. package/template/api/data/users.json +18 -0
  8. package/template/api/mockApi.js +161 -0
  9. package/template/assets/icon.svg +13 -0
  10. package/template/assets/logo.svg +25 -0
  11. package/template/eslint.config.js +94 -0
  12. package/template/index.html +137 -0
  13. package/template/manifest.json +19 -0
  14. package/template/public/.well-known/security.txt +9 -0
  15. package/template/public/_headers +24 -0
  16. package/template/public/_redirects +14 -0
  17. package/template/public/assets/icon.svg +13 -0
  18. package/template/public/assets/logo.svg +25 -0
  19. package/template/public/manifest.json +19 -0
  20. package/template/public/robots.txt +13 -0
  21. package/template/public/sitemap.xml +27 -0
  22. package/template/scripts/build-for-bots.mjs +121 -0
  23. package/template/scripts/convert-to-ts.mjs +106 -0
  24. package/template/scripts/fix-encoding.mjs +38 -0
  25. package/template/scripts/fix-svg-paths.mjs +32 -0
  26. package/template/scripts/generate-cf-router.mjs +52 -0
  27. package/template/scripts/inject-dev-tools.mjs +41 -0
  28. package/template/scripts/inject-version.mjs +65 -0
  29. package/template/scripts/make-component.mjs +445 -0
  30. package/template/scripts/make-component.mjs.backup +432 -0
  31. package/template/scripts/make-controller.mjs +119 -0
  32. package/template/scripts/make-core-component.mjs +303 -0
  33. package/template/scripts/make-view.mjs +346 -0
  34. package/template/scripts/minify.mjs +71 -0
  35. package/template/scripts/prepare-static-assets.mjs +141 -0
  36. package/template/scripts/prompt-bot-build.mjs +223 -0
  37. package/template/scripts/remove-component.mjs +170 -0
  38. package/template/scripts/remove-core-component.mjs +156 -0
  39. package/template/scripts/remove-dev.mjs +13 -0
  40. package/template/scripts/remove-view.mjs +200 -0
  41. package/template/scripts/strip-dev-blocks.mjs +30 -0
  42. package/template/scripts/watch-compile.mjs +69 -0
  43. package/template/server.js +1066 -0
  44. package/template/src/app.ts +115 -0
  45. package/template/src/components/appRegistry.ts +8 -0
  46. package/template/src/components/core/app-footer.ts +27 -0
  47. package/template/src/components/core/app-header.ts +175 -0
  48. package/template/src/components/core/app-sidebar.ts +238 -0
  49. package/template/src/components/core/loading-spinner.ts +25 -0
  50. package/template/src/components/core/nc-a.ts +313 -0
  51. package/template/src/components/core/nc-accordion.ts +186 -0
  52. package/template/src/components/core/nc-alert.ts +153 -0
  53. package/template/src/components/core/nc-animation.ts +1150 -0
  54. package/template/src/components/core/nc-autocomplete.ts +271 -0
  55. package/template/src/components/core/nc-avatar-group.ts +113 -0
  56. package/template/src/components/core/nc-avatar.ts +148 -0
  57. package/template/src/components/core/nc-badge.ts +86 -0
  58. package/template/src/components/core/nc-bottom-nav.ts +214 -0
  59. package/template/src/components/core/nc-breadcrumb.ts +96 -0
  60. package/template/src/components/core/nc-button.ts +307 -0
  61. package/template/src/components/core/nc-card.ts +160 -0
  62. package/template/src/components/core/nc-checkbox.ts +282 -0
  63. package/template/src/components/core/nc-chip.ts +115 -0
  64. package/template/src/components/core/nc-code.ts +314 -0
  65. package/template/src/components/core/nc-collapsible.ts +154 -0
  66. package/template/src/components/core/nc-color-picker.ts +268 -0
  67. package/template/src/components/core/nc-copy-button.ts +119 -0
  68. package/template/src/components/core/nc-date-picker.ts +443 -0
  69. package/template/src/components/core/nc-div.ts +280 -0
  70. package/template/src/components/core/nc-divider.ts +81 -0
  71. package/template/src/components/core/nc-drawer.ts +230 -0
  72. package/template/src/components/core/nc-dropdown.ts +178 -0
  73. package/template/src/components/core/nc-empty-state.ts +134 -0
  74. package/template/src/components/core/nc-file-upload.ts +354 -0
  75. package/template/src/components/core/nc-form.ts +312 -0
  76. package/template/src/components/core/nc-image.ts +184 -0
  77. package/template/src/components/core/nc-input.ts +383 -0
  78. package/template/src/components/core/nc-kbd.ts +48 -0
  79. package/template/src/components/core/nc-menu-item.ts +193 -0
  80. package/template/src/components/core/nc-menu.ts +376 -0
  81. package/template/src/components/core/nc-modal.ts +238 -0
  82. package/template/src/components/core/nc-nav-item.ts +151 -0
  83. package/template/src/components/core/nc-number-input.ts +350 -0
  84. package/template/src/components/core/nc-otp-input.ts +235 -0
  85. package/template/src/components/core/nc-pagination.ts +178 -0
  86. package/template/src/components/core/nc-popover.ts +260 -0
  87. package/template/src/components/core/nc-progress-circular.ts +119 -0
  88. package/template/src/components/core/nc-progress.ts +134 -0
  89. package/template/src/components/core/nc-radio.ts +235 -0
  90. package/template/src/components/core/nc-rating.ts +266 -0
  91. package/template/src/components/core/nc-rich-text.ts +283 -0
  92. package/template/src/components/core/nc-scroll-top.ts +116 -0
  93. package/template/src/components/core/nc-select.ts +452 -0
  94. package/template/src/components/core/nc-skeleton.ts +107 -0
  95. package/template/src/components/core/nc-slider.ts +285 -0
  96. package/template/src/components/core/nc-snackbar.ts +230 -0
  97. package/template/src/components/core/nc-splash.ts +343 -0
  98. package/template/src/components/core/nc-stepper.ts +247 -0
  99. package/template/src/components/core/nc-switch.ts +281 -0
  100. package/template/src/components/core/nc-tab-item.ts +138 -0
  101. package/template/src/components/core/nc-table.ts +279 -0
  102. package/template/src/components/core/nc-tabs.ts +554 -0
  103. package/template/src/components/core/nc-tag-input.ts +279 -0
  104. package/template/src/components/core/nc-textarea.ts +216 -0
  105. package/template/src/components/core/nc-time-picker.ts +438 -0
  106. package/template/src/components/core/nc-timeline.ts +186 -0
  107. package/template/src/components/core/nc-tooltip.ts +143 -0
  108. package/template/src/components/frameworkRegistry.ts +68 -0
  109. package/template/src/components/preloadRegistry.ts +28 -0
  110. package/template/src/components/registry.ts +8 -0
  111. package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
  112. package/template/src/constants/apiEndpoints.ts +27 -0
  113. package/template/src/constants/errorMessages.ts +23 -0
  114. package/template/src/constants/index.ts +8 -0
  115. package/template/src/constants/routePaths.ts +15 -0
  116. package/template/src/constants/storageKeys.ts +18 -0
  117. package/template/src/controllers/dashboard.controller.ts +200 -0
  118. package/template/src/controllers/home.controller.ts +21 -0
  119. package/template/src/controllers/index.ts +11 -0
  120. package/template/src/controllers/login.controller.ts +131 -0
  121. package/template/src/core/component.ts +354 -0
  122. package/template/src/core/errorHandler.ts +85 -0
  123. package/template/src/core/gpu-animation.ts +604 -0
  124. package/template/src/core/http.ts +173 -0
  125. package/template/src/core/lazyComponents.ts +90 -0
  126. package/template/src/core/router.ts +642 -0
  127. package/template/src/core/signals.ts +146 -0
  128. package/template/src/core/state.ts +248 -0
  129. package/template/src/dev/component-editor.ts +1363 -0
  130. package/template/src/dev/component-overlay.ts +278 -0
  131. package/template/src/dev/context-menu.ts +223 -0
  132. package/template/src/dev/denc-tools.ts +250 -0
  133. package/template/src/dev/hmr.ts +189 -0
  134. package/template/src/dev/nfbs.code-workspace +27 -0
  135. package/template/src/dev/outline-panel.ts +1247 -0
  136. package/template/src/middleware/auth.middleware.ts +23 -0
  137. package/template/src/routes/routes.ts +38 -0
  138. package/template/src/services/api.service.ts +394 -0
  139. package/template/src/services/auth.service.ts +176 -0
  140. package/template/src/services/index.ts +8 -0
  141. package/template/src/services/logger.service.ts +74 -0
  142. package/template/src/services/storage.service.ts +88 -0
  143. package/template/src/stores/appStore.ts +57 -0
  144. package/template/src/stores/uiStore.ts +36 -0
  145. package/template/src/styles/core-variables.css +219 -0
  146. package/template/src/styles/core.css +710 -0
  147. package/template/src/styles/main.css +3164 -0
  148. package/template/src/styles/variables.css +152 -0
  149. package/template/src/types/global.d.ts +47 -0
  150. package/template/src/utils/cacheBuster.ts +20 -0
  151. package/template/src/utils/dom.ts +149 -0
  152. package/template/src/utils/events.ts +203 -0
  153. package/template/src/utils/form.ts +176 -0
  154. package/template/src/utils/formatters.ts +169 -0
  155. package/template/src/utils/helpers.ts +195 -0
  156. package/template/src/utils/markdown.ts +307 -0
  157. package/template/src/utils/sidebar.ts +96 -0
  158. package/template/src/utils/smoothScroll.ts +85 -0
  159. package/template/src/utils/templates.ts +23 -0
  160. package/template/src/utils/validation.ts +73 -0
  161. package/template/src/views/protected/dashboard.html +293 -0
  162. package/template/src/views/public/home.html +150 -0
  163. package/template/src/views/public/login.html +102 -0
  164. package/template/tests/unit/component.test.ts +87 -0
  165. package/template/tests/unit/computed.test.ts +79 -0
  166. package/template/tests/unit/form.test.ts +68 -0
  167. package/template/tests/unit/formatters.test.ts +49 -0
  168. package/template/tests/unit/lazy-components.test.ts +59 -0
  169. package/template/tests/unit/markdown.test.ts +62 -0
  170. package/template/tests/unit/router.test.ts +112 -0
  171. package/template/tests/unit/signals.test.ts +54 -0
  172. package/template/tests/unit/validation.test.ts +50 -0
  173. package/template/tsconfig.build.json +21 -0
  174. package/template/tsconfig.json +51 -0
  175. package/template/vitest.config.ts +36 -0
@@ -0,0 +1,432 @@
1
+ #!/usr/bin/env node
2
+
3
+ /**
4
+ * Component Generator Script
5
+ * Like Laravel Artisan for vanilla JS components
6
+ *
7
+ * Usage:
8
+ * node scripts/make-component.mjs counter
9
+ * npm run make:component counter
10
+ */
11
+
12
+ import fs from 'fs';
13
+ import path from 'path';
14
+ import { fileURLToPath } from 'url';
15
+ import readline from 'readline';
16
+
17
+ const __filename = fileURLToPath(import.meta.url);
18
+ const __dirname = path.dirname(__filename);
19
+
20
+ // Get component name from command line
21
+ const componentName = process.argv[2];
22
+
23
+ if (!componentName) {
24
+ console.error('❌ Error: Component name is required');
25
+ console.log('\nUsage:');
26
+ console.log(' npm run make:component <name>');
27
+ console.log('\nExample:');
28
+ console.log(' npm run make:component my-card');
29
+ process.exit(1);
30
+ }
31
+
32
+ // Validate component name (kebab-case with required hyphen)
33
+ if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)+$/.test(componentName)) {
34
+ console.error('❌ Error: Component name must be in kebab-case with at least one hyphen');
35
+ console.error(' Custom elements require a hyphen to avoid conflicts with native HTML elements');
36
+ console.error('\n✅ Valid examples:');
37
+ console.error(' - my-card');
38
+ console.error(' - user-profile');
39
+ console.error(' - sample-component');
40
+ console.error('\n❌ Invalid examples:');
41
+ console.error(' - card (no hyphen)');
42
+ console.error(' - MyCard (not kebab-case)');
43
+ process.exit(1);
44
+ }
45
+
46
+ // Generate class name (PascalCase)
47
+ const className = componentName
48
+ .split('-')
49
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
50
+ .join('');
51
+
52
+ // Component file path (in components/ui folder by default)
53
+ const componentsDir = path.resolve(__dirname, '..', 'src', 'components');
54
+ const uiDir = path.join(componentsDir, 'ui');
55
+ const componentFile = path.join(uiDir, `${componentName}.ts`);
56
+ const registryFile = path.join(componentsDir, 'registry.ts');
57
+
58
+ // Ensure ui directory exists
59
+ if (!fs.existsSync(uiDir)) {
60
+ fs.mkdirSync(uiDir, { recursive: true });
61
+ }
62
+
63
+ // Check if component already exists
64
+ if (fs.existsSync(componentFile)) {
65
+ console.error(`❌ Error: Component "${componentName}.ts" already exists`);
66
+ process.exit(1);
67
+ }
68
+
69
+ // Template files
70
+ const jsTemplate = `/**
71
+ * ${className} Component
72
+ * Generated on ${new Date().toLocaleDateString()}
73
+ *
74
+ * REGISTRATION:
75
+ * This component is automatically registered in src/components/registry.ts
76
+ * It can be used anywhere after the component registry loads:
77
+ *
78
+ * Usage in HTML/Templates:
79
+ * <${componentName}></${componentName}>
80
+ *
81
+ * Usage in other components:
82
+ * <${componentName}></${componentName}>
83
+ *
84
+ * Usage in controllers:
85
+ * const el = document.createElement('${componentName}');
86
+ * document.body.appendChild(el);
87
+ *
88
+ * PERFORMANCE TIPS:
89
+ * - By default, this component is lazy-loaded (loads on first use)
90
+ * - For critical layout components (header, sidebar, footer, layout):
91
+ * Preload by adding to src/components/preloadRegistry.ts:
92
+ * import './ui/${componentName}.js';
93
+ * - For small, reusable components (buttons, cards, modals):
94
+ * Keep lazy loading (better initial load time)
95
+ * - The registry prevents duplicate loading automatically
96
+ */
97
+ import { Component, defineComponent } from '../../core/component.js';
98
+ import { useState, computed, type State, type ComputedState } from '../../core/state.js';
99
+ import { store } from '../../stores/appStore.js';
100
+ import { html } from '../../utils/templates.js';
101
+
102
+ export class ${className} extends Component {
103
+ // Enable Shadow DOM for CSS encapsulation and slot support
104
+ // Uncomment to use scoped styles and <slot> elements
105
+ // static useShadowDOM = true;
106
+
107
+ // ========== Local State ==========
108
+ // count: State<number>;
109
+ // name: State<string>;
110
+
111
+ // ========== Computed Values ==========
112
+ // doubleCount: ComputedState<number>;
113
+ // greeting: ComputedState<string>;
114
+
115
+ // ========== Global Store ==========
116
+ // sharedData: State<number>;
117
+
118
+ constructor() {
119
+ super();
120
+
121
+ // ========== Local State ==========
122
+ // Example reactive state - remove if not needed
123
+ // this.count = useState(0);
124
+ // this.name = useState('');
125
+
126
+ // ========== Computed Values ==========
127
+ // Automatically derived from other state
128
+ // this.doubleCount = computed(() => this.count.value * 2);
129
+ // this.greeting = computed(() => \`Hello, \${this.name.value}!\`);
130
+
131
+ // ========== Global Store ==========
132
+ // Use global store if needed
133
+ // this.sharedData = store.count;
134
+ }
135
+
136
+ template() {
137
+ // TIP: Define CSS variant classes (e.g., .primary, .secondary) below
138
+ // The dev tools will automatically detect them and show in a dropdown
139
+ // This lets you quickly change variants without editing code
140
+
141
+ return html\`
142
+ <style>
143
+ /* :host targets the custom element itself (<${componentName}>) */
144
+ /* Use it to set defaults like display mode, sizing, etc. */
145
+ /* Note: External CSS can override :host styles */
146
+ /* :host {
147
+ display: block;
148
+ width: 100%;
149
+ } */
150
+
151
+ /* :host with attributes - conditional styling */
152
+ /* :host([disabled]) {
153
+ opacity: 0.5;
154
+ pointer-events: none;
155
+ } */
156
+
157
+ /* Styles below only affect content inside shadow DOM */
158
+ /* External CSS cannot reach these (fully encapsulated) */
159
+ .${componentName} {
160
+ padding: var(--spacing-lg);
161
+ background: white;
162
+ border-radius: var(--radius-lg);
163
+ box-shadow: var(--shadow-md);
164
+ }
165
+
166
+ .${componentName} h3 {
167
+ margin-top: 0;
168
+ color: var(--primary);
169
+ }
170
+ </style>
171
+
172
+ <div class="${componentName}">
173
+ <h3>${className}</h3>
174
+ <p>Component content goes here</p>
175
+
176
+ <!-- State watcher example - uncomment to use -->
177
+ <!-- <p id="example-element"></p> -->
178
+
179
+ <!-- Uncomment when Shadow DOM is enabled -->
180
+ <!-- <slot></slot> -->
181
+ </div>
182
+ \`;
183
+ }
184
+
185
+ onMount() {
186
+ // ========== Event Delegation Pattern ==========
187
+ // Use event delegation for reliable event handling
188
+ // This works even if elements are re-rendered
189
+
190
+ // Use shadowRoot if Shadow DOM is enabled, otherwise use this
191
+ const root = this.shadowRoot || this;
192
+
193
+ // ========== Event Delegation Example ==========
194
+ // Uncomment to use event delegation pattern
195
+ // root.addEventListener('click', (e) => {
196
+ // const target = e.target as HTMLElement;
197
+ //
198
+ // // Handle button clicks
199
+ // if (target.matches('.example-button')) {
200
+ // this.handleButtonClick(e);
201
+ // }
202
+ //
203
+ // // Handle link clicks
204
+ // if (target.matches('.example-link')) {
205
+ // e.preventDefault();
206
+ // this.handleLinkClick(e);
207
+ // }
208
+ // });
209
+
210
+ // ========== State Watchers ==========
211
+ // Watch state changes (like React useEffect)
212
+ // this.count.watch(value => {
213
+ // this.$('#example-element').textContent = value;
214
+ // });
215
+
216
+ // ========== Other Event Listeners ==========
217
+ // For events that need to be on specific elements (focus, blur, etc.)
218
+ // const input = this.$('#example-input');
219
+ // if (input) {
220
+ // input.addEventListener('focus', () => console.warn('focused'));
221
+ // }
222
+ }
223
+
224
+ // ========== Event Handlers ==========
225
+ // Uncomment and customize as needed
226
+ // handleButtonClick(e) {
227
+ // console.warn('Button clicked', e.target);
228
+ //
229
+ // // Emit custom event to parent components
230
+ // // this.emitEvent('button-click', { data: 'example' });
231
+ //
232
+ // // Emit with custom options
233
+ // // this.emitEvent('custom-event', { value: 123 }, {
234
+ // // bubbles: true,
235
+ // // composed: true,
236
+ // // cancelable: true
237
+ // // });
238
+ //
239
+ // // Parent components OR page controllers can listen like this:
240
+ // //
241
+ // // In HTML:
242
+ // // <${componentName}></${componentName}>
243
+ // //
244
+ // // In page controller (e.g., home.controller.ts):
245
+ // // import { dom } from '@utils/dom.js';
246
+ // //
247
+ // // export function homeController() {
248
+ // // // Option 1: Using dom.listen() shorthand (recommended)
249
+ // // dom.listen('${componentName}', 'button-click', (e) => {
250
+ // // console.log('Received event:', e.detail); // { data: 'example' }
251
+ // // });
252
+ // //
253
+ // // // Option 2: Traditional way
254
+ // // document.querySelector('${componentName}').addEventListener('button-click', (e) => {
255
+ // // console.log('Received event:', e.detail);
256
+ // // });
257
+ // // }
258
+ // //
259
+ // // Or in another parent component's onMount():
260
+ // // this.shadowRoot.querySelector('${componentName}').addEventListener('button-click', (e) => {
261
+ // // console.log('Child emitted:', e.detail);
262
+ // // });
263
+ // }
264
+ //
265
+ // handleLinkClick(e) {
266
+ // console.warn('Link clicked', e.target);
267
+ //
268
+ // // Example: Emit event with navigation data
269
+ // // this.emitEvent('navigate', { href: e.target.href });
270
+ //
271
+ // // Parent can listen and handle navigation:
272
+ // // import { dom } from '@utils/dom.js';
273
+ // // dom.listen('${componentName}', 'navigate', (e) => {
274
+ // // window.router.navigate(e.detail.href);
275
+ // // });
276
+ // }
277
+
278
+ // Emit custom event to parent components
279
+ // this.emitEvent('button-click', { data: 'example' });
280
+
281
+ // Emit with custom options
282
+ // this.emitEvent('custom-event', { value: 123 }, {
283
+ // bubbles: true,
284
+ // composed: true,
285
+ // cancelable: true
286
+ // });
287
+
288
+ // Parent components OR page controllers can listen like this:
289
+ //
290
+ // In HTML:
291
+ // <${componentName}></${componentName}>
292
+ //
293
+ // In page controller (e.g., home.controller.js):
294
+ // import { dom } from '../utils/dom.js';
295
+ //
296
+ // export function homeController() {
297
+
298
+ onUnmount() {
299
+ // Cleanup if needed
300
+ }
301
+ }
302
+
303
+ defineComponent('${componentName}', ${className});
304
+ `;
305
+
306
+ const readmeTemplate = `# ${className}
307
+
308
+ Custom element: \`<${componentName}>\`
309
+
310
+ ## Usage
311
+
312
+ \`\`\`html
313
+ <${componentName}></${componentName}>
314
+ \`\`\`
315
+
316
+ ## Properties
317
+
318
+ | Property | Type | Default | Description |
319
+ |----------|------|---------|-------------|
320
+ | - | - | - | - |
321
+
322
+ ## Events
323
+
324
+ | Event | Description |
325
+ |-------|-------------|
326
+ | - | - |
327
+
328
+ ## CSS Variables
329
+
330
+ | Variable | Description |
331
+ |----------|-------------|
332
+ | - | - |
333
+
334
+ ## Example
335
+
336
+ \`\`\`html
337
+ <${componentName}></${componentName}>
338
+ \`\`\`
339
+ `;
340
+
341
+ // Write component file
342
+ fs.writeFileSync(componentFile, jsTemplate.trim());
343
+
344
+ // Prompt for prefetch
345
+ const rl = readline.createInterface({
346
+ input: process.stdin,
347
+ output: process.stdout
348
+ });
349
+
350
+ console.log('\n💡 Performance optimization:');
351
+ console.log(' All components are lazy-loaded by default (loads on first use).');
352
+ console.log(' For critical layout components (header, sidebar, footer), prefetching improves performance.\n');
353
+
354
+ rl.question('Would you like to prefetch this component? (y/N): ', (answer) => {
355
+ const shouldPreload = answer.toLowerCase() === 'y' || answer.toLowerCase() === 'yes';
356
+
357
+ if (shouldPreload) {
358
+ // Add import to preloadRegistry.ts
359
+ const preloadFile = path.resolve(__dirname, '..', 'src', 'components', 'preloadRegistry.ts');
360
+
361
+ if (fs.existsSync(preloadFile)) {
362
+ let preloadContent = fs.readFileSync(preloadFile, 'utf-8');
363
+ const importStatement = `import './ui/${componentName}.js';`;
364
+
365
+ // Check if not already imported
366
+ if (!preloadContent.includes(importStatement)) {
367
+ // Find the last import line and add after it
368
+ const lines = preloadContent.split('\n');
369
+ const lastImportIndex = lines.findLastIndex(line =>
370
+ line.trim().startsWith("import './") && line.endsWith(".js';")
371
+ );
372
+
373
+ if (lastImportIndex !== -1) {
374
+ // Add after last import
375
+ lines.splice(lastImportIndex + 1, 0, importStatement);
376
+ } else {
377
+ // Add at the end of the file
378
+ lines.push(importStatement);
379
+ }
380
+
381
+ preloadContent = lines.join('\n');
382
+ fs.writeFileSync(preloadFile, preloadContent);
383
+ console.log('✅ Component will be preloaded (added to preloadRegistry.ts)\n');
384
+ }
385
+ } else {
386
+ console.warn('⚠️ preloadRegistry.ts not found - component will still be lazy-loaded\n');
387
+ }
388
+ } else {
389
+ console.log('✅ Component will be lazy-loaded (default behavior)\n');
390
+ }
391
+
392
+ // Add to registry.ts for lazy loading registration
393
+ if (fs.existsSync(registryFile)) {
394
+ let registryContent = fs.readFileSync(registryFile, 'utf-8');
395
+ const registrationStatement = `componentRegistry.register('${componentName}', './ui/${componentName}.js');`;
396
+
397
+ // Check if already registered
398
+ if (!registryContent.includes(registrationStatement)) {
399
+ // Find the last registration line and add after it
400
+ const lines = registryContent.split('\n');
401
+ const lastRegisterIndex = lines.findLastIndex(line => line.includes('componentRegistry.register'));
402
+
403
+ if (lastRegisterIndex !== -1) {
404
+ // Add after the last registration
405
+ lines.splice(lastRegisterIndex + 1, 0, registrationStatement);
406
+ registryContent = lines.join('\n');
407
+ } else {
408
+ // Add at the end if no registrations found
409
+ registryContent = registryContent.trimEnd() + '\n' + registrationStatement + '\n';
410
+ }
411
+
412
+ fs.writeFileSync(registryFile, registryContent);
413
+ console.log('✅ Component registered in component registry\n');
414
+ }
415
+ } else {
416
+ console.log('⚠️ Note: src/components/registry.ts not found. Manual registration required.\n');
417
+ }
418
+
419
+ // Success message
420
+ console.log(`📁 Location: src/components/ui/${componentName}.ts`);
421
+ console.log(`🔗 Registered in: src/components/registry.ts`);
422
+ if (shouldPreload) {
423
+ console.log(`⚡ Preloaded in: src/components/preloadRegistry.ts`);
424
+ }
425
+ console.log('\n📝 Next steps:');
426
+ console.log(` 1. Edit src/components/ui/${componentName}.ts`);
427
+ console.log(` 2. Use in your HTML: <${componentName}></${componentName}>`);
428
+ console.log('\n🎨 Component class:', className);
429
+ console.log('🏷️ Custom element:', `<${componentName}>`);
430
+
431
+ rl.close();
432
+ });
@@ -0,0 +1,119 @@
1
+ #!/usr/bin/env node
2
+
3
+ import fs from 'fs';
4
+ import path from 'path';
5
+ import { fileURLToPath } from 'url';
6
+ import readline from 'readline';
7
+
8
+ const __filename = fileURLToPath(import.meta.url);
9
+ const __dirname = path.dirname(__filename);
10
+
11
+ const rl = readline.createInterface({
12
+ input: process.stdin,
13
+ output: process.stdout
14
+ });
15
+
16
+ const question = (query) => new Promise((resolve) => rl.question(query, resolve));
17
+
18
+ // Get controller name from command line or prompt
19
+ const controllerArg = process.argv[2];
20
+
21
+ async function main() {
22
+ console.log('NativeCore Controller Generator\n');
23
+
24
+ // Get controller name
25
+ let controllerName = controllerArg;
26
+ if (!controllerName) {
27
+ controllerName = await question('Controller name (e.g., "user-profile"): ');
28
+ }
29
+
30
+ if (!controllerName) {
31
+ console.error('Error: Controller name is required');
32
+ process.exit(1);
33
+ }
34
+
35
+ // Convert to kebab-case and camelCase
36
+ const kebabName = controllerName.toLowerCase().replace(/\s+/g, '-');
37
+ const camelName = kebabName.replace(/-([a-z])/g, (g) => g[1].toUpperCase());
38
+ const titleName = kebabName.split('-').map(w => w.charAt(0).toUpperCase() + w.slice(1)).join(' ');
39
+
40
+ // Paths
41
+ const controllersDir = path.resolve(__dirname, '..', 'src', 'controllers');
42
+ const controllerFile = path.join(controllersDir, `${kebabName}.controller.ts`);
43
+ const indexFile = path.join(controllersDir, 'index.ts');
44
+
45
+ // Check if controller already exists
46
+ if (fs.existsSync(controllerFile)) {
47
+ console.error(`Error: Controller "${kebabName}.controller.ts" already exists`);
48
+ rl.close();
49
+ process.exit(1);
50
+ }
51
+
52
+ // Generate standardized controller template
53
+ const controllerTemplate = `/**
54
+ * ${titleName} Controller
55
+ * Handles dynamic behavior for the ${titleName.toLowerCase()} functionality
56
+ */
57
+ import { trackEvents, trackSubscriptions } from '@utils/events.js';
58
+
59
+ export async function ${camelName}Controller(params: Record<string, string> = {}): Promise<() => void> {
60
+ const events = trackEvents();
61
+ const subs = trackSubscriptions();
62
+
63
+ // DOM references
64
+ // const element = document.getElementById('${kebabName}') as HTMLElement;
65
+
66
+ // Load data, then render and attach events
67
+ // const data = await api.get('/endpoint');
68
+ // element.innerHTML = \`...\`;
69
+
70
+ // Event listeners — all auto-tracked for cleanup
71
+ // events.onClick('#my-button', handleClick);
72
+ // events.delegate('#list', 'click', '.item', handleItemClick);
73
+
74
+ // State watchers — all auto-unsubscribed on cleanup
75
+ // subs.watch(store.isLoading.watch(loading => toggleSpinner(loading)));
76
+
77
+ // Return cleanup function — called automatically on route change
78
+ return () => {
79
+ events.cleanup();
80
+ subs.cleanup();
81
+ };
82
+ }
83
+ `;
84
+
85
+ // Create controller file
86
+ fs.writeFileSync(controllerFile, controllerTemplate);
87
+ console.log(`Created controller: src/controllers/${kebabName}.controller.ts`);
88
+
89
+ // Update index.ts
90
+ if (fs.existsSync(indexFile)) {
91
+ let indexContent = fs.readFileSync(indexFile, 'utf-8');
92
+ const exportLine = `export { ${camelName}Controller } from './${kebabName}.controller.js';\n`;
93
+
94
+ // Check if already exported
95
+ if (!indexContent.includes(`${camelName}Controller`)) {
96
+ indexContent += exportLine;
97
+ fs.writeFileSync(indexFile, indexContent);
98
+ console.log(`Added export to controllers/index.ts`);
99
+ }
100
+ } else {
101
+ // Create index file if it doesn't exist
102
+ const indexContent = `export { ${camelName}Controller } from './${kebabName}.controller.js';\n`;
103
+ fs.writeFileSync(indexFile, indexContent);
104
+ console.log(`Created controllers/index.ts`);
105
+ }
106
+
107
+ console.log('\nController created successfully!');
108
+ console.log(`\nNext steps:`);
109
+ console.log(`1. Register in routes: lazyController('${camelName}Controller', '../controllers/${kebabName}.controller.js')`);
110
+ console.log(`2. Add your logic to: src/controllers/${kebabName}.controller.ts`);
111
+
112
+ rl.close();
113
+ }
114
+
115
+ main().catch(err => {
116
+ console.error('Error:', err.message);
117
+ rl.close();
118
+ process.exit(1);
119
+ });