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
package/bin/index.mjs CHANGED
@@ -3,21 +3,20 @@
3
3
  import { spawn } from 'child_process';
4
4
  import fs from 'fs/promises';
5
5
  import path from 'path';
6
+ import { fileURLToPath } from 'url';
6
7
  import { createInterface } from 'readline/promises';
7
8
  import { stdin as input, stdout as output } from 'process';
8
9
 
9
10
  const cliArgs = process.argv.slice(2);
10
11
  const rl = createInterface({ input, output });
12
+ const __filename = fileURLToPath(import.meta.url);
13
+ const __dirname = path.dirname(__filename);
14
+ const templateDir = path.resolve(__dirname, '../template');
11
15
 
12
16
  function hasFlag(flag) {
13
17
  return cliArgs.includes(flag);
14
18
  }
15
19
 
16
- function getFlagValue(prefix) {
17
- const match = cliArgs.find(arg => arg.startsWith(`${prefix}=`));
18
- return match ? match.slice(prefix.length + 1) : null;
19
- }
20
-
21
20
  function toKebabCase(value) {
22
21
  return value
23
22
  .trim()
@@ -47,15 +46,6 @@ async function askYesNo(question, defaultYes = true) {
47
46
  return answer === 'y' || answer === 'yes';
48
47
  }
49
48
 
50
- async function askChoice(question, options, fallback) {
51
- const label = `${question} [${options.join('/')}]`;
52
- while (true) {
53
- const answer = (await ask(label, fallback)).toLowerCase();
54
- if (options.includes(answer)) return answer;
55
- console.log(`Invalid choice. Expected one of: ${options.join(', ')}`);
56
- }
57
- }
58
-
59
49
  async function ensureDir(dirPath) {
60
50
  await fs.mkdir(dirPath, { recursive: true });
61
51
  }
@@ -65,19 +55,18 @@ async function writeFile(filePath, content) {
65
55
  await fs.writeFile(filePath, content, 'utf8');
66
56
  }
67
57
 
68
- function installCommand(packageManager) {
69
- if (packageManager === 'yarn') {
70
- return { command: 'yarn', args: ['install'] };
71
- }
72
-
73
- return { command: packageManager, args: ['install'] };
58
+ async function removeIfExists(targetPath) {
59
+ await fs.rm(targetPath, { recursive: true, force: true });
74
60
  }
75
61
 
76
- async function installDependencies(targetDir, packageManager) {
77
- const { command, args } = installCommand(packageManager);
62
+ async function replaceInFile(filePath, transform) {
63
+ const existing = await fs.readFile(filePath, 'utf8');
64
+ await fs.writeFile(filePath, transform(existing), 'utf8');
65
+ }
78
66
 
67
+ async function installDependencies(targetDir) {
79
68
  await new Promise((resolve, reject) => {
80
- const child = spawn(command, args, {
69
+ const child = spawn('npm', ['install'], {
81
70
  cwd: targetDir,
82
71
  stdio: 'inherit',
83
72
  shell: process.platform === 'win32'
@@ -90,479 +79,466 @@ async function installDependencies(targetDir, packageManager) {
90
79
  return;
91
80
  }
92
81
 
93
- reject(new Error(`${packageManager} install failed with exit code ${code ?? 'unknown'}`));
82
+ reject(new Error(`npm install failed with exit code ${code ?? 'unknown'}`));
94
83
  });
95
84
  });
96
85
  }
97
86
 
98
- function scriptBlock(config) {
99
- const scripts = {
100
- dev: `${config.packageManager === 'yarn' ? 'yarn compile' : `${config.packageManager} run compile`} && node server.js`,
101
- start: 'node server.js',
102
- compile: config.useTypeScript ? 'tsc' : 'echo "No compile step required for JavaScript"',
103
- typecheck: config.useTypeScript ? 'tsc --noEmit' : 'echo "Type checking is disabled for JavaScript mode"'
104
- };
105
-
106
- return scripts;
107
- }
108
-
109
87
  function packageJsonTemplate(config) {
110
88
  return JSON.stringify({
111
89
  name: config.projectName,
112
90
  version: '0.1.0',
113
- private: true,
91
+ description: `${config.projectTitle} built with NativeCore`,
114
92
  type: 'module',
115
- scripts: scriptBlock(config),
116
- dependencies: {
117
- nativecorejs: config.frameworkDependency
93
+ main: 'server.js',
94
+ scripts: {
95
+ prestart: 'npm run compile && node scripts/inject-version.mjs',
96
+ start: 'node server.js',
97
+ validate: 'npm run typecheck && npm run build:client && npm run test -- --run',
98
+ dev: 'npm run compile && node scripts/inject-version.mjs && concurrently --kill-others --names "watch,server" -c "blue,green" "node scripts/watch-compile.mjs" "node server.js"',
99
+ 'dev:watch': 'node scripts/watch-compile.mjs',
100
+ clean: 'node -e "const fs=require(\'fs\'); fs.rmSync(\'dist\',{recursive:true,force:true}); fs.rmSync(\'_deploy\',{recursive:true,force:true})"',
101
+ prebuild: 'npm run clean && npm run lint && npm run typecheck',
102
+ build: 'node scripts/inject-version.mjs && npm run compile:prod && node scripts/minify.mjs && node scripts/prepare-static-assets.mjs && node scripts/strip-dev-blocks.mjs && node scripts/remove-dev.mjs',
103
+ 'build:client': 'node scripts/inject-version.mjs && npm run compile:prod && node scripts/minify.mjs && node scripts/prepare-static-assets.mjs',
104
+ compile: 'tsc && tsc-alias',
105
+ 'compile:prod': 'tsc -p tsconfig.build.json && tsc-alias -p tsconfig.build.json && node scripts/remove-dev.mjs',
106
+ typecheck: 'tsc --noEmit',
107
+ 'make:component': 'node scripts/make-component.mjs',
108
+ 'make:core-component': 'node scripts/make-core-component.mjs',
109
+ 'make:controller': 'node scripts/make-controller.mjs',
110
+ 'remove:component': 'node scripts/remove-component.mjs',
111
+ 'remove:core-component': 'node scripts/remove-core-component.mjs',
112
+ 'make:view': 'node scripts/make-view.mjs',
113
+ 'remove:view': 'node scripts/remove-view.mjs',
114
+ test: 'vitest',
115
+ 'test:ui': 'vitest --ui',
116
+ 'test:coverage': 'vitest --coverage',
117
+ lint: 'eslint src/**/*.ts && htmlhint "**/*.html" --config .htmlhintrc',
118
+ 'lint:fix': 'eslint src/**/*.ts --fix'
118
119
  },
119
- devDependencies: config.useTypeScript ? {
120
- typescript: '^5.6.3',
121
- '@types/node': '^22.0.0'
122
- } : {}
123
- }, null, 2) + '\n';
124
- }
125
-
126
- function tsconfigTemplate() {
127
- return `{
128
- "compilerOptions": {
129
- "target": "ES2022",
130
- "module": "ESNext",
131
- "moduleResolution": "Bundler",
132
- "outDir": "dist",
133
- "strict": true,
134
- "skipLibCheck": true,
135
- "baseUrl": "."
136
- },
137
- "include": ["src/**/*.ts"]
138
- }
139
- `;
140
- }
141
-
142
- function serverTemplate() {
143
- return `import http from 'http';
144
- import fs from 'fs';
145
- import path from 'path';
146
- import { fileURLToPath } from 'url';
147
-
148
- const __filename = fileURLToPath(import.meta.url);
149
- const __dirname = path.dirname(__filename);
150
- const PORT = process.env.PORT || 8000;
151
-
152
- const mimeTypes = {
153
- '.html': 'text/html',
154
- '.js': 'text/javascript',
155
- '.css': 'text/css',
156
- '.json': 'application/json',
157
- '.svg': 'image/svg+xml'
158
- };
159
-
160
- http.createServer((req, res) => {
161
- const requestPath = req.url?.split('?')[0] || '/';
162
- let filePath = path.join(__dirname, requestPath === '/' ? 'index.html' : requestPath);
163
-
164
- if (!path.extname(requestPath) && !fs.existsSync(filePath)) {
165
- filePath = path.join(__dirname, 'index.html');
166
- }
167
-
168
- fs.readFile(filePath, (error, content) => {
169
- if (error) {
170
- res.writeHead(404, { 'Content-Type': 'text/plain' });
171
- res.end('Not found');
172
- return;
120
+ keywords: ['nativecore', 'spa', 'web-components', 'typescript'],
121
+ license: 'MIT',
122
+ devDependencies: {
123
+ '@eslint/js': '^9.39.2',
124
+ '@types/node': '^20.11.0',
125
+ 'concurrently': '^9.2.1',
126
+ 'eslint': '^9.39.2',
127
+ 'globals': '^17.0.0',
128
+ 'happy-dom': '^20.8.9',
129
+ 'htmlhint': '^1.1.4',
130
+ 'puppeteer': '^24.36.0',
131
+ 'terser': '^5.46.0',
132
+ 'tsc-alias': '^1.8.16',
133
+ 'typescript': '^5.3.3',
134
+ 'typescript-eslint': '^8.53.1',
135
+ 'vitest': '^4.1.4',
136
+ 'ws': '^8.19.0'
173
137
  }
174
-
175
- const contentType = mimeTypes[path.extname(filePath)] || 'text/plain';
176
- res.writeHead(200, { 'Content-Type': contentType, 'Cache-Control': 'no-cache' });
177
- res.end(content);
178
- });
179
- }).listen(PORT, () => {
180
- console.log('NativeCore starter running at http://localhost:' + PORT);
181
- });
182
- `;
138
+ }, null, 2) + '\n';
183
139
  }
184
140
 
185
- function shellHtmlTemplate(config, shell) {
186
- const entryScript = config.useTypeScript ? './dist/app.js' : './src/app.js';
187
- const shellName = shell === 'app' ? 'protected' : 'public';
188
-
189
- return `<!DOCTYPE html>
190
- <html lang="en">
191
- <head>
192
- <meta charset="UTF-8">
193
- <meta name="viewport" content="width=device-width, initial-scale=1.0">
194
- <meta name="app-shell" content="${shellName}">
195
- <title>${config.projectTitle}</title>
196
- <link rel="stylesheet" href="./node_modules/nativecorejs/src/styles/base.css">
197
- <link rel="stylesheet" href="./src/styles/main.css">
198
- <script type="importmap">
199
- {
200
- "imports": {
201
- "nativecorejs": "./node_modules/nativecorejs/dist/index.js",
202
- "nativecorejs/components": "./node_modules/nativecorejs/dist/components/index.js"
141
+ function nativecoreConfigTemplate(config) {
142
+ return JSON.stringify({
143
+ appName: config.projectTitle,
144
+ packageManager: 'npm',
145
+ useTypeScript: true,
146
+ features: {
147
+ auth: config.includeAuth,
148
+ dashboard: config.includeDashboard,
149
+ devTools: true,
150
+ hmr: true,
151
+ mockApi: true
203
152
  }
204
- }
205
- </script>
206
- </head>
207
- <body>
208
- <div id="main-content"></div>
209
- <script type="module" src="${entryScript}"></script>
210
- </body>
211
- </html>
212
- `;
213
- }
214
-
215
- function appEntryTemplate(config) {
216
- return `import { registerBuiltinComponents, Router } from 'nativecorejs';
217
- import { registerRoutes } from './config/routes.js';
218
-
219
- registerBuiltinComponents();
220
-
221
- const router = new Router();
222
-
223
- registerRoutes(router);
224
- router.start();
225
- `;
153
+ }, null, 2) + '\n';
226
154
  }
227
155
 
228
156
  function routesTemplate(config) {
229
- const typeImport = config.useTypeScript
230
- ? "import type { ControllerFunction, Router } from 'nativecorejs';\n"
231
- : '';
232
-
233
- const docsRoute = config.includeDocs
234
- ? " .register('/docs', 'src/views/pages/public/docs.html', lazyController('docsController', '../controllers/docs.controller.js'))\n"
235
- : '';
236
157
  const loginRoute = config.includeAuth
237
- ? " .register('/login', 'src/views/pages/public/login.html', lazyController('loginController', '../controllers/login.controller.js'))\n"
158
+ ? " .register('/login', 'src/views/public/login.html', lazyController('loginController', '../controllers/login.controller.js'))\n"
238
159
  : '';
239
160
  const dashboardRoute = config.includeDashboard
240
- ? " .register('/dashboard', 'src/views/pages/protected/dashboard.html', lazyController('dashboardController', '../controllers/dashboard.controller.js'))\n"
161
+ ? " .register('/dashboard', 'src/views/protected/dashboard.html', lazyController('dashboardController', '../controllers/dashboard.controller.js'))\n"
241
162
  : '';
242
- const protectedRoutes = config.includeDashboard ? "export const protectedRoutes = ['/dashboard'];\n" : "export const protectedRoutes = [];\n";
163
+ const protectedRoutes = config.includeAuth && config.includeDashboard ? "export const protectedRoutes = ['/dashboard'];\n" : "export const protectedRoutes = [];\n";
243
164
 
244
- return `${typeImport}function lazyController(controllerName${config.useTypeScript ? ': string' : ''}, controllerPath${config.useTypeScript ? ': string' : ''})${config.useTypeScript ? ': ControllerFunction' : ''} {
245
- return async (...args) => {
246
- const module = await import(controllerPath);
165
+ return `/**
166
+ * Route Configuration
167
+ */
168
+ import { bustCache } from '../utils/cacheBuster.js';
169
+ import type { ControllerFunction, Router } from '../core/router.js';
170
+
171
+ function lazyController(controllerName: string, controllerPath: string): ControllerFunction {
172
+ return async (...args: any[]) => {
173
+ const module = await import(bustCache(controllerPath));
247
174
  return module[controllerName](...args);
248
175
  };
249
176
  }
250
177
 
251
- export function registerRoutes(router${config.useTypeScript ? ': Router' : ''})${config.useTypeScript ? ': void' : ''} {
178
+ export function registerRoutes(router: Router): void {
252
179
  router
253
- .register('/', 'src/views/pages/public/home.html', lazyController('homeController', '../controllers/home.controller.js'))
254
- ${docsRoute}${loginRoute}${dashboardRoute}}
180
+ .register('/', 'src/views/public/home.html', lazyController('homeController', '../controllers/home.controller.js'))
181
+ ${loginRoute}${dashboardRoute}}
255
182
 
256
183
  ${protectedRoutes}`;
257
184
  }
258
185
 
259
- function controllerTemplate(name, body, config) {
260
- const typePrefix = config.useTypeScript ? ': Promise<() => void>' : '';
261
- return `import { trackEvents, trackSubscriptions } from 'nativecorejs';
262
-
263
- export async function ${name}(params = {})${typePrefix} {
264
- const events = trackEvents();
265
- const subs = trackSubscriptions();
266
-
267
- ${body}
268
-
269
- return () => {
270
- events.cleanup();
271
- subs.cleanup();
272
- };
273
- }
274
- `;
275
- }
276
-
277
- function homeControllerBody(config) {
278
- return ` void params;
279
- const cta = document.querySelector('[data-action="launch-dashboard"]');
280
-
281
- if (cta) {
282
- events.onClick('[data-action="launch-dashboard"]', () => {
283
- window.history.pushState({}, '', '${config.includeDashboard ? '/dashboard' : '/'}');
284
- window.dispatchEvent(new PopStateEvent('popstate'));
285
- });
286
- }`;
287
- }
288
-
289
- function loginControllerBody() {
290
- return ` void params;
291
- events.onSubmit('[data-form="login"]', event => {
292
- event.preventDefault();
293
- });`;
294
- }
186
+ function appTsTemplate(config) {
187
+ const authImports = config.includeAuth
188
+ ? "import auth from './services/auth.service.js';\nimport type { User } from './services/auth.service.js';\nimport api from './services/api.service.js';\nimport { authMiddleware } from './middleware/auth.middleware.js';\n"
189
+ : "";
190
+ const authVerify = config.includeAuth
191
+ ? `async function verifyExistingSession(): Promise<void> {
192
+ if (!auth.getToken()) {
193
+ return;
194
+ }
295
195
 
296
- function docsControllerBody() {
297
- return ' void params;';
298
- }
196
+ try {
197
+ const response = await api.get<{ authenticated: boolean; user?: User }>('/auth/verify');
198
+ if (!response?.authenticated || !response.user) {
199
+ auth.logout();
200
+ return;
201
+ }
299
202
 
300
- function dashboardControllerBody() {
301
- return ` void params;
302
- const items = document.querySelectorAll('[data-metric-card]');
303
- items.forEach(item => item.classList.add('is-ready'));`;
203
+ auth.setUser(response.user);
204
+ } catch {
205
+ auth.logout();
206
+ }
304
207
  }
305
208
 
306
- function publicViewTemplate(config) {
307
- const docsLink = config.includeDocs ? '<a href="/docs">Docs</a>' : '';
308
- const authLink = config.includeAuth ? '<a href="/login">Login</a>' : '';
309
- const dashboardButton = config.includeDashboard ? '<button type="button" data-action="launch-dashboard">Open dashboard shell</button>' : '';
310
-
311
- return `<section class="hero">
312
- <p class="eyebrow">NativeCore</p>
313
- <h1>${config.projectTitle}</h1>
314
- <p class="lede">A clean starter generated by create-nativecore. This shell is app-level only and excludes demo API endpoints or deployment-specific backend assets.</p>
315
- <div class="hero-actions">
316
- ${dashboardButton}
317
- ${docsLink}
318
- ${authLink}
319
- </div>
320
- </section>
321
- `;
322
- }
209
+ `
210
+ : '';
211
+ const authMiddlewareSetup = config.includeAuth ? ' router.use(authMiddleware);\n' : '';
212
+ const authChangeHandler = config.includeAuth ? ` window.addEventListener('auth-change', () => {
213
+ const isAuth = auth.isAuthenticated();
214
+ if (!isAuth) {
215
+ document.body.classList.remove('sidebar-enabled');
216
+ document.getElementById('app')?.classList.remove('sidebar-collapsed');
217
+ document.getElementById('app')?.classList.add('no-sidebar');
218
+ } else {
219
+ updateSidebarVisibility();
220
+ }
221
+ });
323
222
 
324
- function docsViewTemplate() {
325
- return `<section class="page-section">
326
- <h1>Documentation</h1>
327
- <p>Replace this starter page with your product documentation, component examples, or a markdown renderer.</p>
328
- </section>
329
- `;
223
+ ` : '';
224
+ const authVerificationCall = config.includeAuth ? ' await verifyExistingSession();\n' : '';
225
+
226
+ return `/**
227
+ * Main Application Entry Point
228
+ */
229
+ import router from './core/router.js';
230
+ ${authImports}import { registerRoutes, protectedRoutes } from './routes/routes.js';
231
+ import { initSidebar } from './utils/sidebar.js';
232
+ import { initLazyComponents } from './core/lazyComponents.js';
233
+ import './utils/dom.js';
234
+ import './components/registry.js';
235
+
236
+ function isLocalhost(): boolean {
237
+ const hostname = window.location.hostname;
238
+ return hostname === 'localhost' ||
239
+ hostname === '127.0.0.1' ||
240
+ hostname.startsWith('192.168.') ||
241
+ hostname.endsWith('.local');
242
+ }
243
+
244
+ function updateSidebarVisibility() {
245
+ ${config.includeAuth ? ` const isAuthenticated = auth.isAuthenticated();` : ' const isAuthenticated = false;'}
246
+ const currentPath = window.location.pathname;
247
+ const isProtectedRoute = protectedRoutes.some(route => currentPath.startsWith(route));
248
+ const app = document.getElementById('app');
249
+
250
+ if (isAuthenticated && isProtectedRoute) {
251
+ document.body.classList.add('sidebar-enabled');
252
+ app?.classList.remove('no-sidebar');
253
+ } else {
254
+ document.body.classList.remove('sidebar-enabled');
255
+ app?.classList.add('no-sidebar');
256
+ }
330
257
  }
331
258
 
332
- function loginViewTemplate() {
333
- return `<section class="page-section auth-page">
334
- <h1>Sign in</h1>
335
- <form data-form="login" class="auth-form">
336
- <label>
337
- <span>Email</span>
338
- <input type="email" name="email" placeholder="you@example.com">
339
- </label>
340
- <label>
341
- <span>Password</span>
342
- <input type="password" name="password" placeholder="Enter your password">
343
- </label>
344
- <button type="submit">Sign in</button>
345
- </form>
346
- </section>
347
- `;
348
- }
259
+ ${authVerify}async function init() {
260
+ ${authVerificationCall} await initLazyComponents();
349
261
 
350
- function dashboardViewTemplate() {
351
- return `<section class="page-section dashboard-grid">
352
- <article data-metric-card>
353
- <h2>Users</h2>
354
- <p>1,284</p>
355
- </article>
356
- <article data-metric-card>
357
- <h2>Revenue</h2>
358
- <p>$48,900</p>
359
- </article>
360
- <article data-metric-card>
361
- <h2>Errors</h2>
362
- <p>2</p>
363
- </article>
364
- </section>
365
- `;
366
- }
262
+ window.router = router;
367
263
 
368
- function stylesTemplate() {
369
- return `:root {
370
- --background: #f5efe5;
371
- --surface: rgba(255, 255, 255, 0.78);
372
- --surface-strong: #fffaf2;
373
- --text: #1f2937;
374
- --muted: #5b6470;
375
- --accent: #0f766e;
376
- --accent-strong: #115e59;
377
- --border: rgba(31, 41, 55, 0.12);
378
- --shadow: 0 24px 60px rgba(15, 23, 42, 0.10);
379
- font-family: 'Space Grotesk', 'Segoe UI', sans-serif;
380
- }
264
+ ${authMiddlewareSetup} registerRoutes(router);
265
+ router.start();
381
266
 
382
- * {
383
- box-sizing: border-box;
384
- }
267
+ initSidebar();
385
268
 
386
- body {
387
- margin: 0;
388
- min-height: 100vh;
389
- color: var(--text);
390
- background:
391
- radial-gradient(circle at top left, rgba(15, 118, 110, 0.18), transparent 32%),
392
- radial-gradient(circle at bottom right, rgba(217, 119, 6, 0.16), transparent 24%),
393
- linear-gradient(180deg, #f8f5ee 0%, #efe6d8 100%);
394
- }
269
+ ${authChangeHandler} window.addEventListener('pageloaded', () => {
270
+ updateSidebarVisibility();
271
+ });
395
272
 
396
- body::before {
397
- content: '';
398
- position: fixed;
399
- inset: 0;
400
- background-image: linear-gradient(rgba(255, 255, 255, 0.18) 1px, transparent 1px), linear-gradient(90deg, rgba(255, 255, 255, 0.18) 1px, transparent 1px);
401
- background-size: 28px 28px;
402
- pointer-events: none;
403
- opacity: 0.5;
273
+ updateSidebarVisibility();
274
+ initDevTools();
404
275
  }
405
276
 
406
- #main-content {
407
- position: relative;
408
- z-index: 1;
409
- width: min(1100px, calc(100vw - 2rem));
410
- margin: 0 auto;
411
- padding: 4rem 0 5rem;
412
- }
277
+ function initDevTools(): void {
278
+ if (!isLocalhost()) {
279
+ return;
280
+ }
413
281
 
414
- .hero,
415
- .page-section {
416
- background: var(--surface);
417
- backdrop-filter: blur(14px);
418
- border: 1px solid var(--border);
419
- border-radius: 28px;
420
- box-shadow: var(--shadow);
282
+ Promise.all([
283
+ import('./dev/hmr.js'),
284
+ import('./dev/denc-tools.js')
285
+ ])
286
+ .then(() => {
287
+ window.__NATIVECORE_DEV__ = true;
288
+ })
289
+ .catch(() => {
290
+ // Dev tools not available.
291
+ });
421
292
  }
422
293
 
423
- .hero {
424
- padding: 4rem;
294
+ init();
295
+ `;
425
296
  }
426
297
 
427
- .eyebrow {
428
- margin: 0 0 1rem;
429
- text-transform: uppercase;
430
- letter-spacing: 0.18em;
431
- color: var(--accent-strong);
432
- font-size: 0.82rem;
433
- }
298
+ function homeControllerTemplate(config) {
299
+ const authenticatedHref = config.includeDashboard ? '/dashboard' : '/';
300
+ const unauthenticatedHref = config.includeAuth ? '/login' : authenticatedHref;
301
+ const unauthenticatedLabel = config.includeAuth
302
+ ? 'Sign In'
303
+ : config.includeDashboard
304
+ ? 'Open Dashboard'
305
+ : 'Get Started';
306
+
307
+ return `/**
308
+ * Home Controller
309
+ * Updates the primary landing CTA based on authentication status.
310
+ */
311
+ import auth from '../services/auth.service.js';
312
+
313
+ export async function homeController(): Promise<() => void> {
314
+ const getStartedBtn = document.getElementById('get-started-btn') as HTMLAnchorElement | null;
315
+
316
+ if (getStartedBtn) {
317
+ if (auth.isAuthenticated()) {
318
+ getStartedBtn.href = '${authenticatedHref}';
319
+ getStartedBtn.textContent = 'Go to Dashboard';
320
+ } else {
321
+ getStartedBtn.href = '${unauthenticatedHref}';
322
+ getStartedBtn.textContent = '${unauthenticatedLabel}';
323
+ }
324
+ }
434
325
 
435
- h1,
436
- h2,
437
- p {
438
- margin-top: 0;
326
+ return () => {};
439
327
  }
440
-
441
- h1 {
442
- font-size: clamp(2.5rem, 6vw, 5rem);
443
- line-height: 0.95;
444
- margin-bottom: 1rem;
328
+ `;
445
329
  }
446
330
 
447
- .lede,
448
- .page-section p {
449
- max-width: 42rem;
450
- font-size: 1.05rem;
451
- line-height: 1.7;
452
- color: var(--muted);
453
- }
331
+ function homeViewTemplate(config) {
332
+ const secondaryAuth = config.includeAuth ? '<nc-a variant="hero-ghost" href="/login">Sign In</nc-a>' : '';
333
+ const secondaryDashboard = config.includeDashboard ? '<nc-a variant="hero-ghost" href="/dashboard">Dashboard</nc-a>' : '';
454
334
 
455
- .hero-actions {
456
- display: flex;
457
- flex-wrap: wrap;
458
- gap: 0.9rem;
459
- margin-top: 2rem;
335
+ return `<section class="hero">
336
+ <div class="hero-inner">
337
+ <div class="hero-badge">TypeScript-first. Web Components. Dev tools included.</div>
338
+
339
+ <h1>${config.projectTitle}</h1>
340
+
341
+ <p class="hero-tagline">
342
+ This project was scaffolded with NativeCore and includes the full project structure,
343
+ dev tooling, HMR, mock API flow, stores, services, middleware, scripts, and component registries.
344
+ </p>
345
+
346
+ <div class="hero-actions">
347
+ <nc-a variant="hero-primary" href="${config.includeAuth ? '/login' : config.includeDashboard ? '/dashboard' : '/'}" id="get-started-btn">${config.includeAuth ? 'Sign In' : config.includeDashboard ? 'Open Dashboard' : 'Get Started'}</nc-a>
348
+ ${secondaryDashboard}
349
+ ${secondaryAuth}
350
+ </div>
351
+
352
+ <div class="hero-stats">
353
+ <div class="stat-item">
354
+ <span class="stat-number">Full</span>
355
+ <span class="stat-label">Project Template</span>
356
+ </div>
357
+ <div class="stat-item">
358
+ <span class="stat-number">Built-in</span>
359
+ <span class="stat-label">Dev Tools</span>
360
+ </div>
361
+ <div class="stat-item">
362
+ <span class="stat-number">Local</span>
363
+ <span class="stat-label">Mock API</span>
364
+ </div>
365
+ </div>
366
+ </div>
367
+ </section>
368
+ `;
460
369
  }
461
370
 
462
- button,
463
- a {
464
- display: inline-flex;
465
- align-items: center;
466
- justify-content: center;
467
- min-height: 2.9rem;
468
- padding: 0.75rem 1.1rem;
469
- border-radius: 999px;
470
- border: 1px solid transparent;
471
- text-decoration: none;
472
- font: inherit;
371
+ function loginViewTemplate() {
372
+ return `<div class="login-experience">
373
+ <div class="login-shell">
374
+ <section class="login-showcase" aria-label="Starter access overview">
375
+ <div class="login-showcase__eyebrow">Starter Auth Flow</div>
376
+ <h1 class="login-showcase__title">Sign in.</h1>
377
+ <p class="login-showcase__copy">
378
+ This starter includes a local mock authentication flow, protected routes, and dashboard handoff.
379
+ </p>
380
+
381
+ <div class="login-showcase__grid">
382
+ <article class="login-showcase__card">
383
+ <h2>What is included</h2>
384
+ <ul class="login-showcase__list">
385
+ <li>Protected route gating with dashboard handoff</li>
386
+ <li>Mock API-backed authentication for local development</li>
387
+ <li>Component-driven UI built from NativeCore primitives</li>
388
+ </ul>
389
+ </article>
390
+
391
+ <article class="login-showcase__card login-showcase__card--accent">
392
+ <h2>Starter defaults</h2>
393
+ <div class="login-showcase__metrics">
394
+ <div>
395
+ <strong>Demo email</strong>
396
+ <span>demo@example.com</span>
397
+ </div>
398
+ <div>
399
+ <strong>Demo password</strong>
400
+ <span>pa$$w0rd</span>
401
+ </div>
402
+ <div>
403
+ <strong>Target route</strong>
404
+ <span>/dashboard</span>
405
+ </div>
406
+ </div>
407
+ </article>
408
+ </div>
409
+ </section>
410
+
411
+ <section class="login-panel" aria-label="Sign in form">
412
+ <div class="login-panel__header">
413
+ <p class="login-panel__eyebrow">Starter Access</p>
414
+ <h2>Access the dashboard</h2>
415
+ <p>Use the local demo credentials below.</p>
416
+ </div>
417
+
418
+ <div class="login-demo-credentials" aria-label="Demo credentials">
419
+ <div class="login-demo-credentials__item">
420
+ <span>Demo email</span>
421
+ <strong>demo@example.com</strong>
422
+ </div>
423
+ <div class="login-demo-credentials__item">
424
+ <span>Demo password</span>
425
+ <strong>pa$$w0rd</strong>
426
+ </div>
427
+ </div>
428
+
429
+ <div id="login-error" class="login-alert alert alert-error" hidden aria-live="polite"></div>
430
+
431
+ <nc-form id="loginForm" class="login-form">
432
+ <nc-field class="login-field" label="Work Email" for="email" required>
433
+ <nc-input
434
+ id="email"
435
+ name="email"
436
+ type="email"
437
+ autocomplete="username email"
438
+ placeholder="demo@example.com"
439
+ value="demo@example.com"
440
+ required
441
+ ></nc-input>
442
+ </nc-field>
443
+
444
+ <nc-field class="login-field" label="Password" for="password" required>
445
+ <nc-input
446
+ id="password"
447
+ name="password"
448
+ type="password"
449
+ autocomplete="current-password"
450
+ placeholder="Enter your password"
451
+ value="pa$$w0rd"
452
+ required
453
+ minlength="8"
454
+ show-password-toggle
455
+ ></nc-input>
456
+ </nc-field>
457
+
458
+ <div class="login-form__utility">
459
+ <nc-checkbox id="rememberMe" name="rememberMe" label="Remember demo email" checked></nc-checkbox>
460
+ <a href="/" data-link class="login-form__utility-link">Return home</a>
461
+ </div>
462
+
463
+ <nc-button id="loginBtn" type="submit" variant="primary" size="lg" full-width>
464
+ Access Dashboard
465
+ </nc-button>
466
+ </nc-form>
467
+ </section>
468
+ </div>
469
+ </div>
470
+ `;
473
471
  }
474
472
 
475
- button {
476
- background: var(--accent);
477
- color: #fff;
478
- cursor: pointer;
479
- }
473
+ function controllersIndexTemplate(config) {
474
+ const lines = [
475
+ '/**',
476
+ ' * Controller Registry',
477
+ ' */',
478
+ '',
479
+ "export { homeController } from './home.controller.js';"
480
+ ];
480
481
 
481
- button:hover {
482
- background: var(--accent-strong);
483
- }
482
+ if (config.includeAuth) {
483
+ lines.push("export { loginController } from './login.controller.js';");
484
+ }
484
485
 
485
- a {
486
- color: var(--text);
487
- background: rgba(255, 255, 255, 0.65);
488
- border-color: var(--border);
489
- }
486
+ if (config.includeDashboard) {
487
+ lines.push("export { dashboardController } from './dashboard.controller.js';");
488
+ }
490
489
 
491
- .page-section {
492
- padding: 2rem;
490
+ return `${lines.join('\n')}\n`;
493
491
  }
494
492
 
495
- .auth-page {
496
- max-width: 34rem;
493
+ async function copyTemplate(targetDir) {
494
+ await fs.cp(templateDir, targetDir, { recursive: true, force: true });
497
495
  }
498
496
 
499
- .auth-form {
500
- display: grid;
501
- gap: 1rem;
502
- }
497
+ async function customizeProject(targetDir, config) {
498
+ await writeFile(path.join(targetDir, 'package.json'), packageJsonTemplate(config));
499
+ await writeFile(path.join(targetDir, 'nativecore.config.json'), nativecoreConfigTemplate(config));
500
+ await writeFile(path.join(targetDir, 'src/app.ts'), appTsTemplate(config));
501
+ await writeFile(path.join(targetDir, 'src/routes/routes.ts'), routesTemplate(config));
502
+ await writeFile(path.join(targetDir, 'src/controllers/index.ts'), controllersIndexTemplate(config));
503
+ await writeFile(path.join(targetDir, 'src/controllers/home.controller.ts'), homeControllerTemplate(config));
504
+ await writeFile(path.join(targetDir, 'src/views/public/home.html'), homeViewTemplate(config));
503
505
 
504
- .auth-form label {
505
- display: grid;
506
- gap: 0.45rem;
507
- }
506
+ if (config.includeAuth) {
507
+ await writeFile(path.join(targetDir, 'src/views/public/login.html'), loginViewTemplate());
508
+ } else {
509
+ await removeIfExists(path.join(targetDir, 'src/controllers/login.controller.ts'));
510
+ await removeIfExists(path.join(targetDir, 'src/views/public/login.html'));
511
+ }
508
512
 
509
- .auth-form input {
510
- min-height: 3rem;
511
- border: 1px solid var(--border);
512
- border-radius: 16px;
513
- padding: 0.8rem 0.95rem;
514
- font: inherit;
515
- background: rgba(255, 255, 255, 0.85);
516
- }
513
+ if (!config.includeDashboard) {
514
+ await removeIfExists(path.join(targetDir, 'src/controllers/dashboard.controller.ts'));
515
+ await removeIfExists(path.join(targetDir, 'src/views/protected/dashboard.html'));
516
+ }
517
517
 
518
- .dashboard-grid {
519
- display: grid;
520
- grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
521
- gap: 1rem;
522
- }
518
+ await replaceInFile(path.join(targetDir, 'src/services/api.service.ts'), content => content.replace(" return 'https://api.nativecorejs.com';", " return '/api';"));
523
519
 
524
- .dashboard-grid article {
525
- padding: 1.5rem;
526
- border-radius: 20px;
527
- background: var(--surface-strong);
528
- border: 1px solid var(--border);
529
- transform: translateY(8px);
530
- opacity: 0;
531
- transition: transform 160ms ease, opacity 160ms ease;
532
- }
520
+ await replaceInFile(path.join(targetDir, 'src/components/core/app-header.ts'), content => content
521
+ .replace(/<a href="\/docs" data-link class="nanc-link">Docs<\/a>\s*/g, '')
522
+ .replace(/<a href="\/components" data-link class="nanc-link">Components<\/a>\s*/g, '')
523
+ .replace(/<a href="\/docs" data-link class="login-form__utility-link">Review the docs<\/a>/g, '<a href="/" data-link class="login-form__utility-link">Return home</a>'));
533
524
 
534
- .dashboard-grid article.is-ready {
535
- transform: translateY(0);
536
- opacity: 1;
537
- }
525
+ await replaceInFile(path.join(targetDir, 'index.html'), content => content
526
+ .replaceAll('NativeCore | Modern Reactive JavaScript Framework', `${config.projectTitle} | Built with NativeCore`)
527
+ .replaceAll('NativeCore Framework', config.projectTitle)
528
+ .replaceAll('https://nativecorejs.com/', '/')
529
+ .replaceAll('https://nativecorejs.com', '/')
530
+ .replaceAll('@nativecorejs', '')
531
+ .replaceAll('A modern, lightweight reactive framework using vanilla JavaScript, Web Components, reactive signals, and zero dependencies.', `${config.projectTitle} built with NativeCore.`));
538
532
 
539
- @media (max-width: 720px) {
540
- #main-content {
541
- width: min(100vw - 1rem, 100%);
542
- padding-top: 1rem;
543
- }
533
+ await replaceInFile(path.join(targetDir, 'manifest.json'), content => content
534
+ .replace(/"name"\s*:\s*"[^"]+"/, `"name": "${config.projectTitle}"`)
535
+ .replace(/"short_name"\s*:\s*"[^"]+"/, `"short_name": "${config.projectTitle}"`));
544
536
 
545
- .hero,
546
- .page-section {
547
- border-radius: 22px;
548
- padding: 1.4rem;
549
- }
550
- }
551
- `;
552
- }
537
+ await replaceInFile(path.join(targetDir, 'public/_headers'), content => content
538
+ .replace(/https:\/\/api\.nativecorejs\.com\s*/g, '')
539
+ .replace(/Access-Control-Allow-Origin: .*\n/g, ''));
553
540
 
554
- function nativecoreConfigTemplate(config) {
555
- return JSON.stringify({
556
- appName: config.projectTitle,
557
- packageManager: config.packageManager,
558
- useTypeScript: config.useTypeScript,
559
- frameworkDependency: config.frameworkDependency,
560
- features: {
561
- authShell: config.includeAuth,
562
- docs: config.includeDocs,
563
- dashboard: config.includeDashboard
564
- }
565
- }, null, 2) + '\n';
541
+ await replaceInFile(path.join(targetDir, '.env.example'), content => content.replace('APP_NAME=MyApp', `APP_NAME=${config.projectTitle}`));
566
542
  }
567
543
 
568
544
  async function buildProject(config) {
@@ -575,44 +551,9 @@ async function buildProject(config) {
575
551
  if (error.code !== 'ENOENT') throw error;
576
552
  }
577
553
 
578
- const sourceExtension = config.useTypeScript ? 'ts' : 'js';
579
-
580
554
  await ensureDir(targetDir);
581
- await ensureDir(path.join(targetDir, 'src/config'));
582
- await ensureDir(path.join(targetDir, 'src/controllers'));
583
- await ensureDir(path.join(targetDir, 'src/views/pages/public'));
584
- await ensureDir(path.join(targetDir, 'src/views/pages/protected'));
585
- await ensureDir(path.join(targetDir, 'src/styles'));
586
-
587
- await writeFile(path.join(targetDir, 'package.json'), packageJsonTemplate(config));
588
- await writeFile(path.join(targetDir, 'server.js'), serverTemplate());
589
- await writeFile(path.join(targetDir, 'index.html'), shellHtmlTemplate(config, 'index'));
590
- await writeFile(path.join(targetDir, 'nativecore.config.json'), nativecoreConfigTemplate(config));
591
- await writeFile(path.join(targetDir, `src/app.${sourceExtension}`), appEntryTemplate(config));
592
- await writeFile(path.join(targetDir, `src/config/routes.${sourceExtension}`), routesTemplate(config));
593
- await writeFile(path.join(targetDir, `src/controllers/home.controller.${sourceExtension}`), controllerTemplate('homeController', homeControllerBody(config), config));
594
- await writeFile(path.join(targetDir, 'src/views/pages/public/home.html'), publicViewTemplate(config));
595
- await writeFile(path.join(targetDir, 'src/styles/main.css'), stylesTemplate());
596
-
597
- if (config.useTypeScript) {
598
- await writeFile(path.join(targetDir, 'tsconfig.json'), tsconfigTemplate());
599
- }
600
-
601
- if (config.includeAuth) {
602
- await writeFile(path.join(targetDir, 'app.html'), shellHtmlTemplate(config, 'app'));
603
- await writeFile(path.join(targetDir, `src/controllers/login.controller.${sourceExtension}`), controllerTemplate('loginController', loginControllerBody(), config));
604
- await writeFile(path.join(targetDir, 'src/views/pages/public/login.html'), loginViewTemplate());
605
- }
606
-
607
- if (config.includeDocs) {
608
- await writeFile(path.join(targetDir, `src/controllers/docs.controller.${sourceExtension}`), controllerTemplate('docsController', docsControllerBody(), config));
609
- await writeFile(path.join(targetDir, 'src/views/pages/public/docs.html'), docsViewTemplate());
610
- }
611
-
612
- if (config.includeDashboard) {
613
- await writeFile(path.join(targetDir, `src/controllers/dashboard.controller.${sourceExtension}`), controllerTemplate('dashboardController', dashboardControllerBody(), config));
614
- await writeFile(path.join(targetDir, 'src/views/pages/protected/dashboard.html'), dashboardViewTemplate());
615
- }
555
+ await copyTemplate(targetDir);
556
+ await customizeProject(targetDir, config);
616
557
 
617
558
  return targetDir;
618
559
  }
@@ -626,35 +567,16 @@ async function main() {
626
567
  const projectTitle = toTitleCase(projectName);
627
568
  const useDefaults = hasFlag('--defaults');
628
569
 
629
- const useTypeScript = hasFlag('--js')
630
- ? false
631
- : hasFlag('--ts')
632
- ? true
633
- : useDefaults
634
- ? true
635
- : await askYesNo('Use TypeScript?', true);
636
- const useLocalFramework = hasFlag('--local')
637
- ? true
638
- : useDefaults
639
- ? false
640
- : await askYesNo('Use local workspace nativecorejs package?', false);
641
570
  const includeAuth = hasFlag('--no-auth')
642
571
  ? false
643
572
  : useDefaults
644
573
  ? true
645
- : await askYesNo('Include auth shell?', true);
646
- const includeDocs = hasFlag('--no-docs')
647
- ? false
648
- : useDefaults
649
- ? true
650
- : await askYesNo('Include docs route?', true);
574
+ : await askYesNo('Include auth flow?', true);
651
575
  const includeDashboard = hasFlag('--no-dashboard')
652
576
  ? false
653
577
  : useDefaults
654
578
  ? true
655
579
  : await askYesNo('Include dashboard route?', true);
656
- const packageManager = getFlagValue('--pm')
657
- || (useDefaults ? 'npm' : await askChoice('Package manager', ['npm', 'pnpm', 'yarn'], 'npm'));
658
580
  const shouldInstall = hasFlag('--skip-install') || hasFlag('--no-install')
659
581
  ? false
660
582
  : useDefaults
@@ -664,12 +586,8 @@ async function main() {
664
586
  const config = {
665
587
  projectName,
666
588
  projectTitle,
667
- useTypeScript,
668
- frameworkDependency: useLocalFramework ? 'file:../packages/nativecorejs' : '^0.1.0',
669
589
  includeAuth,
670
- includeDocs,
671
590
  includeDashboard,
672
- packageManager,
673
591
  shouldInstall
674
592
  };
675
593
 
@@ -679,10 +597,10 @@ async function main() {
679
597
  let installError = null;
680
598
 
681
599
  if (config.shouldInstall) {
682
- console.log(`\nInstalling dependencies with ${config.packageManager}...\n`);
600
+ console.log('\nInstalling dependencies with npm...\n');
683
601
 
684
602
  try {
685
- await installDependencies(targetDir, config.packageManager);
603
+ await installDependencies(targetDir);
686
604
  installSucceeded = true;
687
605
  } catch (error) {
688
606
  installError = error;
@@ -695,7 +613,7 @@ async function main() {
695
613
  if (config.shouldInstall && installSucceeded) {
696
614
  console.log('Dependencies installed.');
697
615
  } else {
698
- console.log(`${config.packageManager} install`);
616
+ console.log('npm install');
699
617
  }
700
618
 
701
619
  if (installError) {
@@ -703,8 +621,8 @@ async function main() {
703
621
  console.log(installError.message);
704
622
  }
705
623
 
706
- console.log(`${config.packageManager} run dev`);
707
- console.log('\nThis starter expects nativecorejs to provide prebuilt dist files and the base stylesheet from node_modules/nativecorejs.');
624
+ console.log('npm run dev');
625
+ console.log('\nThis scaffold now ships a full NativeCore-style project structure with scripts, dev tools, HMR, services, stores, middleware, mock API, and source folders included.');
708
626
 
709
627
  rl.close();
710
628
  }
@@ -714,4 +632,4 @@ main().catch(error => {
714
632
  console.error(error.message);
715
633
  rl.close();
716
634
  process.exit(1);
717
- });
635
+ });