create-nativecore 0.1.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 (3) hide show
  1. package/README.md +38 -0
  2. package/bin/index.mjs +717 -0
  3. package/package.json +38 -0
package/README.md ADDED
@@ -0,0 +1,38 @@
1
+ # create-nativecore
2
+
3
+ Official CLI for generating NativeCore applications.
4
+
5
+ ## Goals
6
+
7
+ - prompt for common project setup decisions
8
+ - generate app-level shells, routes, controllers, views, and styles
9
+ - keep framework internals inside the published `nativecorejs` package
10
+ - avoid shipping demo API code into new projects by default
11
+
12
+ ## Current starter behavior
13
+
14
+ - installs `nativecorejs` as the framework dependency
15
+ - runs the selected package manager automatically after scaffolding unless `--skip-install` is passed
16
+ - uses an import map so browser-loaded ESM can resolve `nativecorejs` without a bundler
17
+ - links the framework base stylesheet from `node_modules/nativecorejs/src/styles/base.css`
18
+ - registers built-in framework components during app bootstrap
19
+
20
+ ## Local workspace mode
21
+
22
+ - pass `--local` to generate `"nativecorejs": "file:../packages/nativecorejs"`
23
+ - this is intended for working inside the `nativecorejs` monorepo before publishing to npm
24
+ - generated local starters still expect the framework package to be built so `dist/` exists
25
+
26
+ ## Install behavior
27
+
28
+ - `npx create-nativecore my-app` should scaffold the project and run `npm install` immediately by default
29
+ - use `--pm=pnpm` or `--pm=yarn` to install with a different package manager
30
+ - use `--skip-install` when you only want files generated without dependency installation
31
+
32
+ ## Planned templates
33
+
34
+ - landing plus app shell
35
+ - docs starter
36
+ - auth starter
37
+ - dashboard starter
38
+ - future deployment targets such as Cloudflare or Node
package/bin/index.mjs ADDED
@@ -0,0 +1,717 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { spawn } from 'child_process';
4
+ import fs from 'fs/promises';
5
+ import path from 'path';
6
+ import { createInterface } from 'readline/promises';
7
+ import { stdin as input, stdout as output } from 'process';
8
+
9
+ const cliArgs = process.argv.slice(2);
10
+ const rl = createInterface({ input, output });
11
+
12
+ function hasFlag(flag) {
13
+ return cliArgs.includes(flag);
14
+ }
15
+
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
+ function toKebabCase(value) {
22
+ return value
23
+ .trim()
24
+ .toLowerCase()
25
+ .replace(/[^a-z0-9\s-]/g, '')
26
+ .replace(/\s+/g, '-')
27
+ .replace(/-+/g, '-');
28
+ }
29
+
30
+ function toTitleCase(value) {
31
+ return value
32
+ .split(/[-\s]/)
33
+ .filter(Boolean)
34
+ .map(word => word.charAt(0).toUpperCase() + word.slice(1))
35
+ .join(' ');
36
+ }
37
+
38
+ async function ask(question, fallback = '') {
39
+ const suffix = fallback ? ` (${fallback})` : '';
40
+ const answer = await rl.question(`${question}${suffix}: `);
41
+ return answer.trim() || fallback;
42
+ }
43
+
44
+ async function askYesNo(question, defaultYes = true) {
45
+ const fallback = defaultYes ? 'y' : 'n';
46
+ const answer = (await ask(question, fallback)).toLowerCase();
47
+ return answer === 'y' || answer === 'yes';
48
+ }
49
+
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
+ async function ensureDir(dirPath) {
60
+ await fs.mkdir(dirPath, { recursive: true });
61
+ }
62
+
63
+ async function writeFile(filePath, content) {
64
+ await ensureDir(path.dirname(filePath));
65
+ await fs.writeFile(filePath, content, 'utf8');
66
+ }
67
+
68
+ function installCommand(packageManager) {
69
+ if (packageManager === 'yarn') {
70
+ return { command: 'yarn', args: ['install'] };
71
+ }
72
+
73
+ return { command: packageManager, args: ['install'] };
74
+ }
75
+
76
+ async function installDependencies(targetDir, packageManager) {
77
+ const { command, args } = installCommand(packageManager);
78
+
79
+ await new Promise((resolve, reject) => {
80
+ const child = spawn(command, args, {
81
+ cwd: targetDir,
82
+ stdio: 'inherit',
83
+ shell: process.platform === 'win32'
84
+ });
85
+
86
+ child.on('error', reject);
87
+ child.on('exit', code => {
88
+ if (code === 0) {
89
+ resolve();
90
+ return;
91
+ }
92
+
93
+ reject(new Error(`${packageManager} install failed with exit code ${code ?? 'unknown'}`));
94
+ });
95
+ });
96
+ }
97
+
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
+ function packageJsonTemplate(config) {
110
+ return JSON.stringify({
111
+ name: config.projectName,
112
+ version: '0.1.0',
113
+ private: true,
114
+ type: 'module',
115
+ scripts: scriptBlock(config),
116
+ dependencies: {
117
+ nativecorejs: config.frameworkDependency
118
+ },
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;
173
+ }
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
+ `;
183
+ }
184
+
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"
203
+ }
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
+ `;
226
+ }
227
+
228
+ 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
+ const loginRoute = config.includeAuth
237
+ ? " .register('/login', 'src/views/pages/public/login.html', lazyController('loginController', '../controllers/login.controller.js'))\n"
238
+ : '';
239
+ const dashboardRoute = config.includeDashboard
240
+ ? " .register('/dashboard', 'src/views/pages/protected/dashboard.html', lazyController('dashboardController', '../controllers/dashboard.controller.js'))\n"
241
+ : '';
242
+ const protectedRoutes = config.includeDashboard ? "export const protectedRoutes = ['/dashboard'];\n" : "export const protectedRoutes = [];\n";
243
+
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);
247
+ return module[controllerName](...args);
248
+ };
249
+ }
250
+
251
+ export function registerRoutes(router${config.useTypeScript ? ': Router' : ''})${config.useTypeScript ? ': void' : ''} {
252
+ router
253
+ .register('/', 'src/views/pages/public/home.html', lazyController('homeController', '../controllers/home.controller.js'))
254
+ ${docsRoute}${loginRoute}${dashboardRoute}}
255
+
256
+ ${protectedRoutes}`;
257
+ }
258
+
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
+ }
295
+
296
+ function docsControllerBody() {
297
+ return ' void params;';
298
+ }
299
+
300
+ function dashboardControllerBody() {
301
+ return ` void params;
302
+ const items = document.querySelectorAll('[data-metric-card]');
303
+ items.forEach(item => item.classList.add('is-ready'));`;
304
+ }
305
+
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
+ }
323
+
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
+ `;
330
+ }
331
+
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
+ }
349
+
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
+ }
367
+
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
+ }
381
+
382
+ * {
383
+ box-sizing: border-box;
384
+ }
385
+
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
+ }
395
+
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;
404
+ }
405
+
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
+ }
413
+
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);
421
+ }
422
+
423
+ .hero {
424
+ padding: 4rem;
425
+ }
426
+
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
+ }
434
+
435
+ h1,
436
+ h2,
437
+ p {
438
+ margin-top: 0;
439
+ }
440
+
441
+ h1 {
442
+ font-size: clamp(2.5rem, 6vw, 5rem);
443
+ line-height: 0.95;
444
+ margin-bottom: 1rem;
445
+ }
446
+
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
+ }
454
+
455
+ .hero-actions {
456
+ display: flex;
457
+ flex-wrap: wrap;
458
+ gap: 0.9rem;
459
+ margin-top: 2rem;
460
+ }
461
+
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;
473
+ }
474
+
475
+ button {
476
+ background: var(--accent);
477
+ color: #fff;
478
+ cursor: pointer;
479
+ }
480
+
481
+ button:hover {
482
+ background: var(--accent-strong);
483
+ }
484
+
485
+ a {
486
+ color: var(--text);
487
+ background: rgba(255, 255, 255, 0.65);
488
+ border-color: var(--border);
489
+ }
490
+
491
+ .page-section {
492
+ padding: 2rem;
493
+ }
494
+
495
+ .auth-page {
496
+ max-width: 34rem;
497
+ }
498
+
499
+ .auth-form {
500
+ display: grid;
501
+ gap: 1rem;
502
+ }
503
+
504
+ .auth-form label {
505
+ display: grid;
506
+ gap: 0.45rem;
507
+ }
508
+
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
+ }
517
+
518
+ .dashboard-grid {
519
+ display: grid;
520
+ grid-template-columns: repeat(auto-fit, minmax(220px, 1fr));
521
+ gap: 1rem;
522
+ }
523
+
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
+ }
533
+
534
+ .dashboard-grid article.is-ready {
535
+ transform: translateY(0);
536
+ opacity: 1;
537
+ }
538
+
539
+ @media (max-width: 720px) {
540
+ #main-content {
541
+ width: min(100vw - 1rem, 100%);
542
+ padding-top: 1rem;
543
+ }
544
+
545
+ .hero,
546
+ .page-section {
547
+ border-radius: 22px;
548
+ padding: 1.4rem;
549
+ }
550
+ }
551
+ `;
552
+ }
553
+
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';
566
+ }
567
+
568
+ async function buildProject(config) {
569
+ const targetDir = path.resolve(process.cwd(), config.projectName);
570
+
571
+ try {
572
+ await fs.access(targetDir);
573
+ throw new Error(`Target directory already exists: ${config.projectName}`);
574
+ } catch (error) {
575
+ if (error.code !== 'ENOENT') throw error;
576
+ }
577
+
578
+ const sourceExtension = config.useTypeScript ? 'ts' : 'js';
579
+
580
+ 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
+ }
616
+
617
+ return targetDir;
618
+ }
619
+
620
+ async function main() {
621
+ console.log('\nNativeCore installer\n');
622
+
623
+ const positionalName = cliArgs.find(arg => !arg.startsWith('--'));
624
+ const rawName = positionalName || await ask('Project name', 'my-nativecore-app');
625
+ const projectName = toKebabCase(rawName);
626
+ const projectTitle = toTitleCase(projectName);
627
+ const useDefaults = hasFlag('--defaults');
628
+
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
+ const includeAuth = hasFlag('--no-auth')
642
+ ? false
643
+ : useDefaults
644
+ ? 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);
651
+ const includeDashboard = hasFlag('--no-dashboard')
652
+ ? false
653
+ : useDefaults
654
+ ? true
655
+ : await askYesNo('Include dashboard route?', true);
656
+ const packageManager = getFlagValue('--pm')
657
+ || (useDefaults ? 'npm' : await askChoice('Package manager', ['npm', 'pnpm', 'yarn'], 'npm'));
658
+ const shouldInstall = hasFlag('--skip-install') || hasFlag('--no-install')
659
+ ? false
660
+ : useDefaults
661
+ ? true
662
+ : await askYesNo('Install dependencies now?', true);
663
+
664
+ const config = {
665
+ projectName,
666
+ projectTitle,
667
+ useTypeScript,
668
+ frameworkDependency: useLocalFramework ? 'file:../packages/nativecorejs' : '^0.1.0',
669
+ includeAuth,
670
+ includeDocs,
671
+ includeDashboard,
672
+ packageManager,
673
+ shouldInstall
674
+ };
675
+
676
+ const targetDir = await buildProject(config);
677
+
678
+ let installSucceeded = false;
679
+ let installError = null;
680
+
681
+ if (config.shouldInstall) {
682
+ console.log(`\nInstalling dependencies with ${config.packageManager}...\n`);
683
+
684
+ try {
685
+ await installDependencies(targetDir, config.packageManager);
686
+ installSucceeded = true;
687
+ } catch (error) {
688
+ installError = error;
689
+ }
690
+ }
691
+
692
+ console.log('\nProject created successfully.');
693
+ console.log(`cd ${config.projectName}`);
694
+
695
+ if (config.shouldInstall && installSucceeded) {
696
+ console.log('Dependencies installed.');
697
+ } else {
698
+ console.log(`${config.packageManager} install`);
699
+ }
700
+
701
+ if (installError) {
702
+ console.log('\nDependency installation did not complete.');
703
+ console.log(installError.message);
704
+ }
705
+
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.');
708
+
709
+ rl.close();
710
+ }
711
+
712
+ main().catch(error => {
713
+ console.error('\nFailed to scaffold NativeCore app.');
714
+ console.error(error.message);
715
+ rl.close();
716
+ process.exit(1);
717
+ });
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "create-nativecore",
3
+ "version": "0.1.0",
4
+ "description": "Interactive CLI for scaffolding NativeCore applications",
5
+ "keywords": [
6
+ "nativecore",
7
+ "cli",
8
+ "scaffold",
9
+ "starter",
10
+ "web-components"
11
+ ],
12
+ "homepage": "https://github.com/davidtv2008/nativecorejs#readme",
13
+ "bugs": {
14
+ "url": "https://github.com/davidtv2008/nativecorejs/issues"
15
+ },
16
+ "repository": {
17
+ "type": "git",
18
+ "url": "git+https://github.com/davidtv2008/nativecorejs.git"
19
+ },
20
+ "type": "module",
21
+ "bin": {
22
+ "create-nativecore": "./bin/index.mjs"
23
+ },
24
+ "files": [
25
+ "bin",
26
+ "README.md"
27
+ ],
28
+ "scripts": {
29
+ "build": "echo create-nativecore is a direct CLI package"
30
+ },
31
+ "publishConfig": {
32
+ "access": "public"
33
+ },
34
+ "engines": {
35
+ "node": ">=18"
36
+ },
37
+ "license": "MIT"
38
+ }