create-nativecore 0.1.1 → 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.
- package/README.md +6 -14
- package/bin/index.mjs +402 -431
- package/package.json +3 -2
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Core Component Generator Script
|
|
5
|
+
* Creates framework core components with --nc- variables
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/make-core-component.mjs button
|
|
9
|
+
* npm run make:core-component button
|
|
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:core-component <name>');
|
|
27
|
+
console.log('\nExample:');
|
|
28
|
+
console.log(' npm run make:core-component button');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
// Validate component name (no hyphen needed for core components - we add nc- prefix)
|
|
33
|
+
if (!/^[a-z][a-z0-9-]*$/.test(componentName)) {
|
|
34
|
+
console.error('Error: Component name must be lowercase with optional hyphens');
|
|
35
|
+
console.error('\nValid examples:');
|
|
36
|
+
console.error(' - button');
|
|
37
|
+
console.error(' - card');
|
|
38
|
+
console.error(' - input');
|
|
39
|
+
console.error(' - dropdown-menu');
|
|
40
|
+
process.exit(1);
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
// Generate names
|
|
44
|
+
const tagName = `nc-${componentName}`; // e.g., nc-button
|
|
45
|
+
const className = tagName
|
|
46
|
+
.split('-')
|
|
47
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
48
|
+
.join(''); // e.g., NcButton
|
|
49
|
+
|
|
50
|
+
// Component file path (in components/core folder)
|
|
51
|
+
const componentsDir = path.resolve(__dirname, '..', 'src', 'components');
|
|
52
|
+
const coreDir = path.join(componentsDir, 'core');
|
|
53
|
+
const componentFile = path.join(coreDir, `${tagName}.ts`);
|
|
54
|
+
const preloadRegistryFile = path.join(componentsDir, 'preloadRegistry.ts');
|
|
55
|
+
|
|
56
|
+
// Ensure core directory exists
|
|
57
|
+
if (!fs.existsSync(coreDir)) {
|
|
58
|
+
fs.mkdirSync(coreDir, { recursive: true });
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// Check if component already exists
|
|
62
|
+
if (fs.existsSync(componentFile)) {
|
|
63
|
+
console.error(`Error: Component "${tagName}.ts" already exists`);
|
|
64
|
+
process.exit(1);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// Template for core component
|
|
68
|
+
const componentTemplate = `/**
|
|
69
|
+
* ${className} Component
|
|
70
|
+
*
|
|
71
|
+
* NativeCore Framework Core Component
|
|
72
|
+
* Generated on ${new Date().toLocaleDateString()}
|
|
73
|
+
*
|
|
74
|
+
* FRAMEWORK INTERNAL COMPONENT
|
|
75
|
+
* This component uses --nc- CSS variables from core-variables.css
|
|
76
|
+
* for consistent styling across the framework.
|
|
77
|
+
*
|
|
78
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
79
|
+
* DEV TOOLS INTEGRATION - How to make attributes editable in sidebar:
|
|
80
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
81
|
+
*
|
|
82
|
+
* 1. ADD TO observedAttributes:
|
|
83
|
+
* static get observedAttributes() {
|
|
84
|
+
* return ['variant', 'size', 'disabled']; // ← These become editable
|
|
85
|
+
* }
|
|
86
|
+
*
|
|
87
|
+
* 2. USE getAttribute() IN template():
|
|
88
|
+
* const variant = this.getAttribute('variant') || 'primary';
|
|
89
|
+
* const size = this.getAttribute('size') || 'md';
|
|
90
|
+
*
|
|
91
|
+
* 3. FOR DROPDOWN SELECTORS (variant/size):
|
|
92
|
+
* Name attributes 'variant' or 'size' AND use CSS patterns:
|
|
93
|
+
*
|
|
94
|
+
* Option A - :host() selectors (RECOMMENDED for core components):
|
|
95
|
+
* :host([variant="primary"]) { ... }
|
|
96
|
+
* :host([variant="secondary"]) { ... }
|
|
97
|
+
* :host([size="sm"]) { ... }
|
|
98
|
+
* :host([size="lg"]) { ... }
|
|
99
|
+
*
|
|
100
|
+
* Option B - Class selectors:
|
|
101
|
+
* .primary { ... }
|
|
102
|
+
* .secondary { ... }
|
|
103
|
+
* .btn-sm { ... }
|
|
104
|
+
* .btn-lg { ... }
|
|
105
|
+
*
|
|
106
|
+
* The dev tools will auto-detect these patterns and create dropdowns!
|
|
107
|
+
*
|
|
108
|
+
* 4. ATTRIBUTE TYPES (auto-detected):
|
|
109
|
+
* - Boolean: disabled, loading, hidden, readonly → Checkbox
|
|
110
|
+
* - Variant/Size: variant, size (with CSS) → Dropdown
|
|
111
|
+
* - Number: count, max, min, step → Slider
|
|
112
|
+
* - Everything else → Text input
|
|
113
|
+
*
|
|
114
|
+
* 5. LIVE UPDATES:
|
|
115
|
+
* Implement attributeChangedCallback for instant preview updates
|
|
116
|
+
*
|
|
117
|
+
* ═══════════════════════════════════════════════════════════════════
|
|
118
|
+
*
|
|
119
|
+
* Usage:
|
|
120
|
+
* <${tagName} variant="primary"></${tagName}>
|
|
121
|
+
* <${tagName} variant="secondary" size="lg"></${tagName}>
|
|
122
|
+
*
|
|
123
|
+
* Attributes:
|
|
124
|
+
* - variant: Component style variant
|
|
125
|
+
* - size: Component size (sm, md, lg)
|
|
126
|
+
* - disabled: Disabled state
|
|
127
|
+
*/
|
|
128
|
+
|
|
129
|
+
import { Component, defineComponent } from '@core/component.js';
|
|
130
|
+
import { html } from '@utils/templates.js';
|
|
131
|
+
|
|
132
|
+
export class ${className} extends Component {
|
|
133
|
+
static useShadowDOM = true;
|
|
134
|
+
|
|
135
|
+
// ═══ Define dropdown options for dev tools (auto-detected) ═══
|
|
136
|
+
static attributeOptions = {
|
|
137
|
+
variant: ['primary', 'secondary', 'success', 'danger'],
|
|
138
|
+
size: ['sm', 'md', 'lg']
|
|
139
|
+
};
|
|
140
|
+
|
|
141
|
+
// ═══ Attributes listed here become editable in dev tools sidebar ═══
|
|
142
|
+
static get observedAttributes() {
|
|
143
|
+
return ['variant', 'size', 'disabled'];
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
constructor() {
|
|
147
|
+
super();
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
template() {
|
|
151
|
+
// ═══ Use getAttribute() - dev tools reads this to detect attributes ═══
|
|
152
|
+
const variant = this.getAttribute('variant') || 'primary';
|
|
153
|
+
const size = this.getAttribute('size') || 'md';
|
|
154
|
+
const disabled = this.hasAttribute('disabled');
|
|
155
|
+
|
|
156
|
+
return html\`
|
|
157
|
+
<style>
|
|
158
|
+
:host {
|
|
159
|
+
display: inline-block;
|
|
160
|
+
font-family: var(--nc-font-family);
|
|
161
|
+
padding: var(--nc-spacing-md);
|
|
162
|
+
border-radius: var(--nc-radius-md);
|
|
163
|
+
transition: all var(--nc-transition-fast);
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
/* ═══ Variant Options (auto-detected for dropdown) ═══ */
|
|
167
|
+
/* Dev tools will scan :host([variant="..."]) patterns */
|
|
168
|
+
|
|
169
|
+
:host([variant="primary"]) {
|
|
170
|
+
background: var(--nc-gradient-primary);
|
|
171
|
+
color: var(--nc-white);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
:host([variant="secondary"]) {
|
|
175
|
+
background: var(--nc-bg-secondary);
|
|
176
|
+
color: var(--nc-text);
|
|
177
|
+
border: 1px solid var(--nc-border);
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
:host([variant="success"]) {
|
|
181
|
+
background: var(--nc-gradient-success);
|
|
182
|
+
color: var(--nc-white);
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
:host([variant="danger"]) {
|
|
186
|
+
background: var(--nc-gradient-danger);
|
|
187
|
+
color: var(--nc-white);
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
/* ═══ Size Options (auto-detected for dropdown) ═══ */
|
|
191
|
+
/* Dev tools will scan :host([size="..."]) patterns */
|
|
192
|
+
|
|
193
|
+
:host([size="sm"]) {
|
|
194
|
+
padding: var(--nc-spacing-sm);
|
|
195
|
+
font-size: var(--nc-font-size-sm);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
:host([size="md"]) {
|
|
199
|
+
padding: var(--nc-spacing-md);
|
|
200
|
+
font-size: var(--nc-font-size-base);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
:host([size="lg"]) {
|
|
204
|
+
padding: var(--nc-spacing-lg);
|
|
205
|
+
font-size: var(--nc-font-size-lg);
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/* ═══ Disabled State (auto-detected as checkbox) ═══ */
|
|
209
|
+
:host([disabled]) {
|
|
210
|
+
opacity: 0.5;
|
|
211
|
+
pointer-events: none;
|
|
212
|
+
}
|
|
213
|
+
</style>
|
|
214
|
+
|
|
215
|
+
<slot></slot>
|
|
216
|
+
\`;
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
onMount() {
|
|
220
|
+
// Component logic here
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// ═══ Makes changes instant in dev tools preview ═══
|
|
224
|
+
attributeChangedCallback(name: string, oldValue: string, newValue: string) {
|
|
225
|
+
if (oldValue !== newValue && this._mounted) {
|
|
226
|
+
this.render();
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
|
|
231
|
+
defineComponent('${tagName}', ${className});
|
|
232
|
+
`;
|
|
233
|
+
|
|
234
|
+
// Create readline interface for prompts
|
|
235
|
+
const rl = readline.createInterface({
|
|
236
|
+
input: process.stdin,
|
|
237
|
+
output: process.stdout
|
|
238
|
+
});
|
|
239
|
+
|
|
240
|
+
function prompt(question) {
|
|
241
|
+
return new Promise((resolve) => {
|
|
242
|
+
rl.question(question, resolve);
|
|
243
|
+
});
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
async function main() {
|
|
247
|
+
console.log(`\nCreating NativeCore framework component: ${tagName}\n`);
|
|
248
|
+
|
|
249
|
+
// Ask about preloading
|
|
250
|
+
const preload = await prompt('Preload this component? (Y/n): ');
|
|
251
|
+
const shouldPreload = !preload || preload.toLowerCase() !== 'n';
|
|
252
|
+
|
|
253
|
+
// Create component file
|
|
254
|
+
fs.writeFileSync(componentFile, componentTemplate);
|
|
255
|
+
console.log(`Created: src/components/core/${tagName}.ts`);
|
|
256
|
+
|
|
257
|
+
// Add to preloadRegistry if requested
|
|
258
|
+
if (shouldPreload) {
|
|
259
|
+
let registryContent = fs.readFileSync(preloadRegistryFile, 'utf-8');
|
|
260
|
+
|
|
261
|
+
// Find the import section for core components
|
|
262
|
+
const coreImportSection = registryContent.indexOf('// Core framework components');
|
|
263
|
+
|
|
264
|
+
if (coreImportSection !== -1) {
|
|
265
|
+
// Find the next line after the comment
|
|
266
|
+
const insertPosition = registryContent.indexOf('\n', coreImportSection) + 1;
|
|
267
|
+
const importStatement = `import './core/${tagName}.js';\n`;
|
|
268
|
+
|
|
269
|
+
// Check if import already exists
|
|
270
|
+
if (!registryContent.includes(importStatement)) {
|
|
271
|
+
registryContent = registryContent.slice(0, insertPosition) +
|
|
272
|
+
importStatement +
|
|
273
|
+
registryContent.slice(insertPosition);
|
|
274
|
+
|
|
275
|
+
fs.writeFileSync(preloadRegistryFile, registryContent);
|
|
276
|
+
console.log(`Added to: src/components/preloadRegistry.ts`);
|
|
277
|
+
}
|
|
278
|
+
} else {
|
|
279
|
+
console.log('Warning: Could not find core components section in preloadRegistry.ts');
|
|
280
|
+
console.log(' Please manually add: import \'./core/${tagName}.js\';');
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
console.log('\nFramework component created successfully!\n');
|
|
285
|
+
console.log('Usage:');
|
|
286
|
+
console.log(` <${tagName} variant="primary">Content</${tagName}>`);
|
|
287
|
+
console.log(` <${tagName} variant="secondary" size="lg">Large</${tagName}>`);
|
|
288
|
+
console.log('\nDev Tools Integration:');
|
|
289
|
+
console.log(' • Attributes in observedAttributes → Editable in sidebar');
|
|
290
|
+
console.log(' • :host([variant="..."]) patterns → Auto-detected dropdown');
|
|
291
|
+
console.log(' • :host([size="..."]) patterns → Auto-detected dropdown');
|
|
292
|
+
console.log(' • Boolean attrs (disabled, etc.) → Checkbox');
|
|
293
|
+
console.log(' • attributeChangedCallback → Live preview updates');
|
|
294
|
+
console.log('\nComponent uses --nc- variables from core-variables.css');
|
|
295
|
+
console.log('Check the generated file for detailed integration comments.\n');
|
|
296
|
+
|
|
297
|
+
rl.close();
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
main().catch(error => {
|
|
301
|
+
console.error('Error:', error.message);
|
|
302
|
+
process.exit(1);
|
|
303
|
+
});
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* View Generator Script
|
|
5
|
+
* Creates views (HTML pages) with optional controllers
|
|
6
|
+
*
|
|
7
|
+
* Usage:
|
|
8
|
+
* node scripts/make-view.mjs profile
|
|
9
|
+
* npm run make:view profile
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import fs from 'fs';
|
|
13
|
+
import path from 'path';
|
|
14
|
+
import readline from 'readline';
|
|
15
|
+
import { fileURLToPath } from 'url';
|
|
16
|
+
import { generateCfRouter } from './generate-cf-router.mjs';
|
|
17
|
+
|
|
18
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
19
|
+
const __dirname = path.dirname(__filename);
|
|
20
|
+
|
|
21
|
+
// Get view name from command line
|
|
22
|
+
const viewName = process.argv[2];
|
|
23
|
+
|
|
24
|
+
if (!viewName) {
|
|
25
|
+
console.error('Error: View name is required');
|
|
26
|
+
console.log('\nUsage:');
|
|
27
|
+
console.log(' npm run make:view <name>');
|
|
28
|
+
console.log('\nExample:');
|
|
29
|
+
console.log(' npm run make:view profile');
|
|
30
|
+
process.exit(1);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
// Validate view name (kebab-case)
|
|
34
|
+
if (!/^[a-z][a-z0-9]*(-[a-z0-9]+)*$/.test(viewName)) {
|
|
35
|
+
console.error('Error: View name must be in kebab-case (lowercase with hyphens)');
|
|
36
|
+
console.error('\nValid examples:');
|
|
37
|
+
console.error(' - profile');
|
|
38
|
+
console.error(' - user-profile');
|
|
39
|
+
console.error(' - my-settings');
|
|
40
|
+
console.error('\nInvalid examples:');
|
|
41
|
+
console.error(' - Profile (not lowercase)');
|
|
42
|
+
console.error(' - user_profile (underscore instead of hyphen)');
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// Create readline interface for prompts
|
|
47
|
+
const rl = readline.createInterface({
|
|
48
|
+
input: process.stdin,
|
|
49
|
+
output: process.stdout
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
// Promisify question
|
|
53
|
+
function question(query) {
|
|
54
|
+
return new Promise(resolve => rl.question(query, resolve));
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Generate controller name (camelCase)
|
|
58
|
+
const controllerName = viewName
|
|
59
|
+
.split('-')
|
|
60
|
+
.map((word, index) =>
|
|
61
|
+
index === 0 ? word : word.charAt(0).toUpperCase() + word.slice(1)
|
|
62
|
+
)
|
|
63
|
+
.join('');
|
|
64
|
+
|
|
65
|
+
// Generate title (Title Case)
|
|
66
|
+
const viewTitle = viewName
|
|
67
|
+
.split('-')
|
|
68
|
+
.map(word => word.charAt(0).toUpperCase() + word.slice(1))
|
|
69
|
+
.join(' ');
|
|
70
|
+
|
|
71
|
+
async function generateView() {
|
|
72
|
+
try {
|
|
73
|
+
// Prompt for route access level within the single-shell SPA
|
|
74
|
+
const isProtectedAnswer = await question('Should this route require login? (y/n): ');
|
|
75
|
+
const isProtected = isProtectedAnswer.toLowerCase().trim() === 'y';
|
|
76
|
+
|
|
77
|
+
// Prompt for controller creation
|
|
78
|
+
const createControllerAnswer = await question('Create a controller for this view? (y/n): ');
|
|
79
|
+
const createController = createControllerAnswer.toLowerCase().trim() === 'y';
|
|
80
|
+
|
|
81
|
+
rl.close();
|
|
82
|
+
|
|
83
|
+
// Determine directories
|
|
84
|
+
const accessFolder = isProtected ? 'protected' : 'public';
|
|
85
|
+
const viewsDir = path.resolve(__dirname, '..', 'src', 'views', accessFolder);
|
|
86
|
+
const viewFile = path.join(viewsDir, `${viewName}.html`);
|
|
87
|
+
|
|
88
|
+
// Check if view already exists
|
|
89
|
+
if (fs.existsSync(viewFile)) {
|
|
90
|
+
console.error(`Error: View "${viewName}.html" already exists in ${accessFolder} folder`);
|
|
91
|
+
process.exit(1);
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Ensure directory exists
|
|
95
|
+
if (!fs.existsSync(viewsDir)) {
|
|
96
|
+
fs.mkdirSync(viewsDir, { recursive: true });
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Generate HTML template
|
|
100
|
+
const htmlTemplate = createController ? `<div class="${viewName}-page">
|
|
101
|
+
<h1 id="${viewName}-title">${viewTitle}</h1>
|
|
102
|
+
<p>This is the ${viewTitle} page.</p>
|
|
103
|
+
|
|
104
|
+
<div id="${viewName}-content">
|
|
105
|
+
<loading-spinner message="Loading ${viewTitle.toLowerCase()}..."></loading-spinner>
|
|
106
|
+
</div>
|
|
107
|
+
</div>
|
|
108
|
+
` : `<div class="${viewName}-page">
|
|
109
|
+
<h1>${viewTitle}</h1>
|
|
110
|
+
<p>This is the ${viewTitle} page.</p>
|
|
111
|
+
|
|
112
|
+
<div class="content">
|
|
113
|
+
<!-- Add your content here -->
|
|
114
|
+
</div>
|
|
115
|
+
</div>
|
|
116
|
+
`;
|
|
117
|
+
|
|
118
|
+
// Create HTML file
|
|
119
|
+
fs.writeFileSync(viewFile, htmlTemplate);
|
|
120
|
+
console.log(`Created view: src/views/${accessFolder}/${viewName}.html`);
|
|
121
|
+
|
|
122
|
+
// Create controller if requested
|
|
123
|
+
if (createController) {
|
|
124
|
+
const controllersDir = path.resolve(__dirname, '..', 'src', 'controllers');
|
|
125
|
+
const controllerFile = path.join(controllersDir, `${viewName}.controller.ts`);
|
|
126
|
+
const indexFile = path.join(controllersDir, 'index.ts');
|
|
127
|
+
|
|
128
|
+
// Check if controller already exists
|
|
129
|
+
if (fs.existsSync(controllerFile)) {
|
|
130
|
+
console.error(`Warning: Controller "${viewName}.controller.ts" already exists`);
|
|
131
|
+
} else {
|
|
132
|
+
// Generate controller template
|
|
133
|
+
const controllerTemplate = `/**
|
|
134
|
+
* ${viewTitle} Page Controller
|
|
135
|
+
* Handles dynamic behavior for the ${viewTitle} page
|
|
136
|
+
*/
|
|
137
|
+
import { trackEvents, trackSubscriptions } from '@utils/events.js';
|
|
138
|
+
${isProtected ? "import auth from '@services/auth.service.js';\nimport api from '@services/api.service.js';\n" : ""}
|
|
139
|
+
export async function ${controllerName}Controller(params: Record<string, string> = {}): Promise<() => void> {
|
|
140
|
+
const events = trackEvents();
|
|
141
|
+
const subs = trackSubscriptions();
|
|
142
|
+
|
|
143
|
+
const titleElement = document.getElementById('${viewName}-title') as HTMLElement | null;
|
|
144
|
+
const contentElement = document.getElementById('${viewName}-content') as HTMLElement | null;
|
|
145
|
+
if (!contentElement) return () => { events.cleanup(); subs.cleanup(); };
|
|
146
|
+
|
|
147
|
+
${isProtected ? `const user = auth.getUser();
|
|
148
|
+
if (user && titleElement) {
|
|
149
|
+
titleElement.textContent = \`${viewTitle} - \${user.name}\`;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
` : ''}try {
|
|
153
|
+
${isProtected ? `// const data = await api.get('/${viewName}');
|
|
154
|
+
|
|
155
|
+
contentElement.innerHTML = \`
|
|
156
|
+
<div class="card">
|
|
157
|
+
<h2>Welcome to ${viewTitle}</h2>
|
|
158
|
+
<p>Your dynamic content goes here.</p>
|
|
159
|
+
<nc-button id="${viewName}-btn" variant="primary">Action</nc-button>
|
|
160
|
+
</div>
|
|
161
|
+
\`;
|
|
162
|
+
|
|
163
|
+
events.onClick('#${viewName}-btn', handleAction);` : `contentElement.innerHTML = \`
|
|
164
|
+
<div class="card">
|
|
165
|
+
<h2>Content Section</h2>
|
|
166
|
+
<p>Your content goes here.</p>
|
|
167
|
+
</div>
|
|
168
|
+
\`;`}
|
|
169
|
+
} catch (error) {
|
|
170
|
+
contentElement.innerHTML = \`
|
|
171
|
+
<div class="alert alert-error">
|
|
172
|
+
Failed to load ${viewTitle.toLowerCase()}: \${(error as Error).message}
|
|
173
|
+
</div>
|
|
174
|
+
\`;
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
return () => {
|
|
178
|
+
events.cleanup();
|
|
179
|
+
subs.cleanup();
|
|
180
|
+
};
|
|
181
|
+
|
|
182
|
+
function handleAction() {
|
|
183
|
+
// Handle action button click
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
`;
|
|
187
|
+
|
|
188
|
+
// Create controller file
|
|
189
|
+
fs.writeFileSync(controllerFile, controllerTemplate);
|
|
190
|
+
console.log(`Created controller: src/controllers/${viewName}.controller.ts`);
|
|
191
|
+
|
|
192
|
+
// Update controllers/index.ts
|
|
193
|
+
if (fs.existsSync(indexFile)) {
|
|
194
|
+
let indexContent = fs.readFileSync(indexFile, 'utf8');
|
|
195
|
+
|
|
196
|
+
// Add export using .js extension (ES module requirement)
|
|
197
|
+
const importStatement = `export { ${controllerName}Controller } from './${viewName}.controller.js';\n`;
|
|
198
|
+
|
|
199
|
+
// Check if import already exists
|
|
200
|
+
if (!indexContent.includes(`${viewName}.controller`)) {
|
|
201
|
+
// Add to end of file
|
|
202
|
+
indexContent += importStatement;
|
|
203
|
+
fs.writeFileSync(indexFile, indexContent);
|
|
204
|
+
console.log(`Updated: src/controllers/index.ts`);
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
// Update routes.ts for lazy loading
|
|
211
|
+
const routesPath = path.resolve(__dirname, '..', 'src', 'routes', 'routes.ts');
|
|
212
|
+
if (fs.existsSync(routesPath)) {
|
|
213
|
+
let routesContent = fs.readFileSync(routesPath, 'utf8');
|
|
214
|
+
|
|
215
|
+
// Add route registration with lazy controller
|
|
216
|
+
const routeRegistration = createController
|
|
217
|
+
? `.register('/${viewName}', 'src/views/${accessFolder}/${viewName}.html', lazyController('${controllerName}Controller', '../controllers/${viewName}.controller.js'))`
|
|
218
|
+
: `.register('/${viewName}', 'src/views/${accessFolder}/${viewName}.html')`;
|
|
219
|
+
|
|
220
|
+
// Find the last .register() call and add after it
|
|
221
|
+
const lastRegisterIndex = routesContent.lastIndexOf('.register(');
|
|
222
|
+
if (lastRegisterIndex !== -1) {
|
|
223
|
+
// Find the end of this line (semicolon or newline)
|
|
224
|
+
const lineEndIndex = routesContent.indexOf('\n', lastRegisterIndex);
|
|
225
|
+
if (lineEndIndex !== -1) {
|
|
226
|
+
// Insert new route after this line
|
|
227
|
+
const insertPoint = lineEndIndex;
|
|
228
|
+
routesContent = routesContent.slice(0, insertPoint) +
|
|
229
|
+
'\n ' + routeRegistration +
|
|
230
|
+
routesContent.slice(insertPoint);
|
|
231
|
+
console.log(`Added route registration to src/routes/routes.ts`);
|
|
232
|
+
}
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
// Add to protected routes array if needed
|
|
236
|
+
if (isProtected) {
|
|
237
|
+
const protectedRoutesRegex = /export const protectedRoutes = \[(.*?)\];/s;
|
|
238
|
+
const match = routesContent.match(protectedRoutesRegex);
|
|
239
|
+
if (match) {
|
|
240
|
+
const routesList = match[1].trim();
|
|
241
|
+
if (routesList && !routesList.includes(`'/${viewName}'`)) {
|
|
242
|
+
const newRoutes = routesList + `, '/${viewName}'`;
|
|
243
|
+
routesContent = routesContent.replace(
|
|
244
|
+
protectedRoutesRegex,
|
|
245
|
+
`export const protectedRoutes = [${newRoutes}];`
|
|
246
|
+
);
|
|
247
|
+
console.log(`Added '/${viewName}' to protected routes array`);
|
|
248
|
+
}
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
fs.writeFileSync(routesPath, routesContent);
|
|
253
|
+
generateCfRouter();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
// Update sidebar.ts if protected view
|
|
257
|
+
if (isProtected) {
|
|
258
|
+
try {
|
|
259
|
+
const sidebarPath = path.resolve(__dirname, '..', 'src', 'utils', 'sidebar.ts');
|
|
260
|
+
if (fs.existsSync(sidebarPath)) {
|
|
261
|
+
let sidebarContent = fs.readFileSync(sidebarPath, 'utf8');
|
|
262
|
+
|
|
263
|
+
// Add const declaration for the new link
|
|
264
|
+
const linkDeclaration = `const ${viewName}Link = document.querySelector('.sidebar-item.${viewName}-link') as HTMLElement;`;
|
|
265
|
+
const componentsLinkMatch = sidebarContent.match(/const componentsLink[^\n]*\n/);
|
|
266
|
+
|
|
267
|
+
if (componentsLinkMatch) {
|
|
268
|
+
sidebarContent = sidebarContent.replace(
|
|
269
|
+
componentsLinkMatch[0],
|
|
270
|
+
componentsLinkMatch[0] + ' ' + linkDeclaration + '\n'
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
// Add display toggle
|
|
275
|
+
const displayToggle = `if (${viewName}Link) ${viewName}Link.style.display = isAuthenticated ? 'flex' : 'none';`;
|
|
276
|
+
const componentsDisplayMatch = sidebarContent.match(/if \(componentsLink\)[^\n]*\n/);
|
|
277
|
+
|
|
278
|
+
if (componentsDisplayMatch) {
|
|
279
|
+
sidebarContent = sidebarContent.replace(
|
|
280
|
+
componentsDisplayMatch[0],
|
|
281
|
+
componentsDisplayMatch[0] + ' ' + displayToggle + '\n'
|
|
282
|
+
);
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
fs.writeFileSync(sidebarPath, sidebarContent);
|
|
286
|
+
console.log(`Updated sidebar.ts for new link visibility`);
|
|
287
|
+
}
|
|
288
|
+
} catch (e) {
|
|
289
|
+
// sidebar.ts update is optional
|
|
290
|
+
}
|
|
291
|
+
}
|
|
292
|
+
|
|
293
|
+
// Show next steps
|
|
294
|
+
console.log('\nNext steps:');
|
|
295
|
+
|
|
296
|
+
// Automatically add to navigation
|
|
297
|
+
try {
|
|
298
|
+
const indexPath = path.resolve(__dirname, '..', 'index.html');
|
|
299
|
+
let indexContent = fs.readFileSync(indexPath, 'utf-8');
|
|
300
|
+
|
|
301
|
+
if (isProtected) {
|
|
302
|
+
// Add to sidebar if protected
|
|
303
|
+
const sidebarPattern = /<a href="\/components"[^>]*class="sidebar-item components-link"[^>]*>[\s\S]*?<\/a>\s*<button class="sidebar-item logout-link"/;
|
|
304
|
+
const navLink = `<a href="/components" data-link class="sidebar-item components-link" style="display: none;">
|
|
305
|
+
<span class="sidebar-icon"></span>
|
|
306
|
+
<span class="sidebar-text">Components</span>
|
|
307
|
+
</a>
|
|
308
|
+
<a href="/${viewName}" data-link class="sidebar-item ${viewName}-link" style="display: none;">
|
|
309
|
+
<span class="sidebar-icon"></span>
|
|
310
|
+
<span class="sidebar-text">${viewTitle}</span>
|
|
311
|
+
</a>
|
|
312
|
+
<button class="sidebar-item logout-link"`;
|
|
313
|
+
|
|
314
|
+
if (sidebarPattern.test(indexContent)) {
|
|
315
|
+
indexContent = indexContent.replace(sidebarPattern, navLink);
|
|
316
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
317
|
+
console.log(`Added to sidebar menu (protected pages)`);
|
|
318
|
+
}
|
|
319
|
+
} else {
|
|
320
|
+
// Add to header nav if public
|
|
321
|
+
const headerPattern = /<a href="\/about"[^>]*class="nanc-link"[^>]*>About<\/a>\s*<\/nav>/;
|
|
322
|
+
const headerLink = `<a href="/about" data-link class="nanc-link">About</a>
|
|
323
|
+
<a href="/${viewName}" data-link class="nanc-link">${viewTitle}</a>
|
|
324
|
+
</nav>`;
|
|
325
|
+
|
|
326
|
+
if (headerPattern.test(indexContent)) {
|
|
327
|
+
indexContent = indexContent.replace(headerPattern, headerLink);
|
|
328
|
+
fs.writeFileSync(indexPath, indexContent);
|
|
329
|
+
console.log(`Added to public header navigation`);
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
} catch (e) {
|
|
333
|
+
console.log(`Note: Could not auto-add navigation link. Add manually if needed.`);
|
|
334
|
+
}
|
|
335
|
+
|
|
336
|
+
console.log('\nDone!\n');
|
|
337
|
+
|
|
338
|
+
} catch (error) {
|
|
339
|
+
console.error('Error:', error.message);
|
|
340
|
+
rl.close();
|
|
341
|
+
process.exit(1);
|
|
342
|
+
}
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
// Run the generator
|
|
346
|
+
generateView();
|