create-nativecore 0.1.0 → 0.1.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (3) hide show
  1. package/README.md +5 -5
  2. package/bin/index.mjs +46 -99
  3. package/package.json +2 -2
package/README.md CHANGED
@@ -4,35 +4,35 @@ Official CLI for generating NativeCore applications.
4
4
 
5
5
  ## Goals
6
6
 
7
- - prompt for common project setup decisions
7
+ - prompt only for app-level starter decisions that actually vary
8
8
  - generate app-level shells, routes, controllers, views, and styles
9
9
  - keep framework internals inside the published `nativecorejs` package
10
10
  - avoid shipping demo API code into new projects by default
11
11
 
12
12
  ## Current starter behavior
13
13
 
14
+ - always generates a TypeScript project
14
15
  - installs `nativecorejs` as the framework dependency
15
- - runs the selected package manager automatically after scaffolding unless `--skip-install` is passed
16
+ - uses npm for dependency installation by default unless `--skip-install` is passed
16
17
  - uses an import map so browser-loaded ESM can resolve `nativecorejs` without a bundler
17
18
  - links the framework base stylesheet from `node_modules/nativecorejs/src/styles/base.css`
18
19
  - registers built-in framework components during app bootstrap
20
+ - does not generate documentation routes or docs pages
19
21
 
20
22
  ## Local workspace mode
21
23
 
22
24
  - pass `--local` to generate `"nativecorejs": "file:../packages/nativecorejs"`
23
- - this is intended for working inside the `nativecorejs` monorepo before publishing to npm
25
+ - this is only supported from the `nativecorejs` monorepo root before publishing to npm
24
26
  - generated local starters still expect the framework package to be built so `dist/` exists
25
27
 
26
28
  ## Install behavior
27
29
 
28
30
  - `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
31
  - use `--skip-install` when you only want files generated without dependency installation
31
32
 
32
33
  ## Planned templates
33
34
 
34
35
  - landing plus app shell
35
- - docs starter
36
36
  - auth starter
37
37
  - dashboard starter
38
38
  - future deployment targets such as Cloudflare or Node
package/bin/index.mjs CHANGED
@@ -13,11 +13,6 @@ function hasFlag(flag) {
13
13
  return cliArgs.includes(flag);
14
14
  }
15
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
16
  function toKebabCase(value) {
22
17
  return value
23
18
  .trim()
@@ -47,15 +42,6 @@ async function askYesNo(question, defaultYes = true) {
47
42
  return answer === 'y' || answer === 'yes';
48
43
  }
49
44
 
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
45
  async function ensureDir(dirPath) {
60
46
  await fs.mkdir(dirPath, { recursive: true });
61
47
  }
@@ -65,16 +51,9 @@ async function writeFile(filePath, content) {
65
51
  await fs.writeFile(filePath, content, 'utf8');
66
52
  }
67
53
 
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);
54
+ async function installDependencies(targetDir) {
55
+ const command = 'npm';
56
+ const args = ['install'];
78
57
 
79
58
  await new Promise((resolve, reject) => {
80
59
  const child = spawn(command, args, {
@@ -90,17 +69,17 @@ async function installDependencies(targetDir, packageManager) {
90
69
  return;
91
70
  }
92
71
 
93
- reject(new Error(`${packageManager} install failed with exit code ${code ?? 'unknown'}`));
72
+ reject(new Error(`npm install failed with exit code ${code ?? 'unknown'}`));
94
73
  });
95
74
  });
96
75
  }
97
76
 
98
77
  function scriptBlock(config) {
99
78
  const scripts = {
100
- dev: `${config.packageManager === 'yarn' ? 'yarn compile' : `${config.packageManager} run compile`} && node server.js`,
79
+ dev: 'npm run compile && node server.js',
101
80
  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"'
81
+ compile: 'tsc',
82
+ typecheck: 'tsc --noEmit'
104
83
  };
105
84
 
106
85
  return scripts;
@@ -116,10 +95,10 @@ function packageJsonTemplate(config) {
116
95
  dependencies: {
117
96
  nativecorejs: config.frameworkDependency
118
97
  },
119
- devDependencies: config.useTypeScript ? {
98
+ devDependencies: {
120
99
  typescript: '^5.6.3',
121
100
  '@types/node': '^22.0.0'
122
- } : {}
101
+ }
123
102
  }, null, 2) + '\n';
124
103
  }
125
104
 
@@ -183,7 +162,7 @@ http.createServer((req, res) => {
183
162
  }
184
163
 
185
164
  function shellHtmlTemplate(config, shell) {
186
- const entryScript = config.useTypeScript ? './dist/app.js' : './src/app.js';
165
+ const entryScript = './dist/app.js';
187
166
  const shellName = shell === 'app' ? 'protected' : 'public';
188
167
 
189
168
  return `<!DOCTYPE html>
@@ -226,13 +205,7 @@ router.start();
226
205
  }
227
206
 
228
207
  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
- : '';
208
+ const typeImport = "import type { ControllerFunction, Router } from 'nativecorejs';\n";
236
209
  const loginRoute = config.includeAuth
237
210
  ? " .register('/login', 'src/views/pages/public/login.html', lazyController('loginController', '../controllers/login.controller.js'))\n"
238
211
  : '';
@@ -241,26 +214,26 @@ function routesTemplate(config) {
241
214
  : '';
242
215
  const protectedRoutes = config.includeDashboard ? "export const protectedRoutes = ['/dashboard'];\n" : "export const protectedRoutes = [];\n";
243
216
 
244
- return `${typeImport}function lazyController(controllerName${config.useTypeScript ? ': string' : ''}, controllerPath${config.useTypeScript ? ': string' : ''})${config.useTypeScript ? ': ControllerFunction' : ''} {
245
- return async (...args) => {
217
+ return `${typeImport}function lazyController(controllerName: string, controllerPath: string): ControllerFunction {
218
+ return async (...args: Parameters<ControllerFunction>) => {
246
219
  const module = await import(controllerPath);
247
- return module[controllerName](...args);
220
+ const controller = module[controllerName] as ControllerFunction;
221
+ return controller(...args);
248
222
  };
249
223
  }
250
224
 
251
- export function registerRoutes(router${config.useTypeScript ? ': Router' : ''})${config.useTypeScript ? ': void' : ''} {
225
+ export function registerRoutes(router: Router): void {
252
226
  router
253
227
  .register('/', 'src/views/pages/public/home.html', lazyController('homeController', '../controllers/home.controller.js'))
254
- ${docsRoute}${loginRoute}${dashboardRoute}}
228
+ ${loginRoute}${dashboardRoute}}
255
229
 
256
230
  ${protectedRoutes}`;
257
231
  }
258
232
 
259
233
  function controllerTemplate(name, body, config) {
260
- const typePrefix = config.useTypeScript ? ': Promise<() => void>' : '';
261
234
  return `import { trackEvents, trackSubscriptions } from 'nativecorejs';
262
235
 
263
- export async function ${name}(params = {})${typePrefix} {
236
+ export async function ${name}(params: Record<string, string> = {}): Promise<() => void> {
264
237
  const events = trackEvents();
265
238
  const subs = trackSubscriptions();
266
239
 
@@ -288,15 +261,11 @@ function homeControllerBody(config) {
288
261
 
289
262
  function loginControllerBody() {
290
263
  return ` void params;
291
- events.onSubmit('[data-form="login"]', event => {
264
+ events.onSubmit('[data-form="login"]', (event: Event) => {
292
265
  event.preventDefault();
293
266
  });`;
294
267
  }
295
268
 
296
- function docsControllerBody() {
297
- return ' void params;';
298
- }
299
-
300
269
  function dashboardControllerBody() {
301
270
  return ` void params;
302
271
  const items = document.querySelectorAll('[data-metric-card]');
@@ -304,7 +273,6 @@ function dashboardControllerBody() {
304
273
  }
305
274
 
306
275
  function publicViewTemplate(config) {
307
- const docsLink = config.includeDocs ? '<a href="/docs">Docs</a>' : '';
308
276
  const authLink = config.includeAuth ? '<a href="/login">Login</a>' : '';
309
277
  const dashboardButton = config.includeDashboard ? '<button type="button" data-action="launch-dashboard">Open dashboard shell</button>' : '';
310
278
 
@@ -314,21 +282,12 @@ function publicViewTemplate(config) {
314
282
  <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
283
  <div class="hero-actions">
316
284
  ${dashboardButton}
317
- ${docsLink}
318
285
  ${authLink}
319
286
  </div>
320
287
  </section>
321
288
  `;
322
289
  }
323
290
 
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
291
  function loginViewTemplate() {
333
292
  return `<section class="page-section auth-page">
334
293
  <h1>Sign in</h1>
@@ -554,17 +513,25 @@ a {
554
513
  function nativecoreConfigTemplate(config) {
555
514
  return JSON.stringify({
556
515
  appName: config.projectTitle,
557
- packageManager: config.packageManager,
558
- useTypeScript: config.useTypeScript,
516
+ packageManager: 'npm',
517
+ useTypeScript: true,
559
518
  frameworkDependency: config.frameworkDependency,
560
519
  features: {
561
520
  authShell: config.includeAuth,
562
- docs: config.includeDocs,
563
521
  dashboard: config.includeDashboard
564
522
  }
565
523
  }, null, 2) + '\n';
566
524
  }
567
525
 
526
+ async function supportsLocalWorkspace() {
527
+ try {
528
+ await fs.access(path.join(process.cwd(), 'packages', 'nativecorejs', 'package.json'));
529
+ return true;
530
+ } catch {
531
+ return false;
532
+ }
533
+ }
534
+
568
535
  async function buildProject(config) {
569
536
  const targetDir = path.resolve(process.cwd(), config.projectName);
570
537
 
@@ -575,7 +542,7 @@ async function buildProject(config) {
575
542
  if (error.code !== 'ENOENT') throw error;
576
543
  }
577
544
 
578
- const sourceExtension = config.useTypeScript ? 'ts' : 'js';
545
+ const sourceExtension = 'ts';
579
546
 
580
547
  await ensureDir(targetDir);
581
548
  await ensureDir(path.join(targetDir, 'src/config'));
@@ -594,9 +561,7 @@ async function buildProject(config) {
594
561
  await writeFile(path.join(targetDir, 'src/views/pages/public/home.html'), publicViewTemplate(config));
595
562
  await writeFile(path.join(targetDir, 'src/styles/main.css'), stylesTemplate());
596
563
 
597
- if (config.useTypeScript) {
598
- await writeFile(path.join(targetDir, 'tsconfig.json'), tsconfigTemplate());
599
- }
564
+ await writeFile(path.join(targetDir, 'tsconfig.json'), tsconfigTemplate());
600
565
 
601
566
  if (config.includeAuth) {
602
567
  await writeFile(path.join(targetDir, 'app.html'), shellHtmlTemplate(config, 'app'));
@@ -604,11 +569,6 @@ async function buildProject(config) {
604
569
  await writeFile(path.join(targetDir, 'src/views/pages/public/login.html'), loginViewTemplate());
605
570
  }
606
571
 
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
572
  if (config.includeDashboard) {
613
573
  await writeFile(path.join(targetDir, `src/controllers/dashboard.controller.${sourceExtension}`), controllerTemplate('dashboardController', dashboardControllerBody(), config));
614
574
  await writeFile(path.join(targetDir, 'src/views/pages/protected/dashboard.html'), dashboardViewTemplate());
@@ -626,35 +586,23 @@ async function main() {
626
586
  const projectTitle = toTitleCase(projectName);
627
587
  const useDefaults = hasFlag('--defaults');
628
588
 
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);
589
+ const wantsLocalFramework = hasFlag('--local');
590
+ const canUseLocalFramework = wantsLocalFramework ? await supportsLocalWorkspace() : false;
591
+
592
+ if (wantsLocalFramework && !canUseLocalFramework) {
593
+ throw new Error('--local can only be used from the nativecorejs monorepo root where ./packages/nativecorejs exists.');
594
+ }
595
+
641
596
  const includeAuth = hasFlag('--no-auth')
642
597
  ? false
643
598
  : useDefaults
644
599
  ? true
645
600
  : 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
601
  const includeDashboard = hasFlag('--no-dashboard')
652
602
  ? false
653
603
  : useDefaults
654
604
  ? true
655
605
  : await askYesNo('Include dashboard route?', true);
656
- const packageManager = getFlagValue('--pm')
657
- || (useDefaults ? 'npm' : await askChoice('Package manager', ['npm', 'pnpm', 'yarn'], 'npm'));
658
606
  const shouldInstall = hasFlag('--skip-install') || hasFlag('--no-install')
659
607
  ? false
660
608
  : useDefaults
@@ -664,12 +612,11 @@ async function main() {
664
612
  const config = {
665
613
  projectName,
666
614
  projectTitle,
667
- useTypeScript,
668
- frameworkDependency: useLocalFramework ? 'file:../packages/nativecorejs' : '^0.1.0',
615
+ useTypeScript: true,
616
+ frameworkDependency: wantsLocalFramework ? 'file:../packages/nativecorejs' : '^0.1.0',
669
617
  includeAuth,
670
- includeDocs,
671
618
  includeDashboard,
672
- packageManager,
619
+ packageManager: 'npm',
673
620
  shouldInstall
674
621
  };
675
622
 
@@ -679,10 +626,10 @@ async function main() {
679
626
  let installError = null;
680
627
 
681
628
  if (config.shouldInstall) {
682
- console.log(`\nInstalling dependencies with ${config.packageManager}...\n`);
629
+ console.log('\nInstalling dependencies with npm...\n');
683
630
 
684
631
  try {
685
- await installDependencies(targetDir, config.packageManager);
632
+ await installDependencies(targetDir);
686
633
  installSucceeded = true;
687
634
  } catch (error) {
688
635
  installError = error;
@@ -695,7 +642,7 @@ async function main() {
695
642
  if (config.shouldInstall && installSucceeded) {
696
643
  console.log('Dependencies installed.');
697
644
  } else {
698
- console.log(`${config.packageManager} install`);
645
+ console.log('npm install');
699
646
  }
700
647
 
701
648
  if (installError) {
@@ -703,7 +650,7 @@ async function main() {
703
650
  console.log(installError.message);
704
651
  }
705
652
 
706
- console.log(`${config.packageManager} run dev`);
653
+ console.log('npm run dev');
707
654
  console.log('\nThis starter expects nativecorejs to provide prebuilt dist files and the base stylesheet from node_modules/nativecorejs.');
708
655
 
709
656
  rl.close();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "create-nativecore",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "Interactive CLI for scaffolding NativeCore applications",
5
5
  "keywords": [
6
6
  "nativecore",
@@ -19,7 +19,7 @@
19
19
  },
20
20
  "type": "module",
21
21
  "bin": {
22
- "create-nativecore": "./bin/index.mjs"
22
+ "create-nativecore": "bin/index.mjs"
23
23
  },
24
24
  "files": [
25
25
  "bin",