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.
- package/README.md +5 -5
- package/bin/index.mjs +46 -99
- 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
|
|
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
|
-
-
|
|
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
|
|
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
|
|
69
|
-
|
|
70
|
-
|
|
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(
|
|
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:
|
|
79
|
+
dev: 'npm run compile && node server.js',
|
|
101
80
|
start: 'node server.js',
|
|
102
|
-
compile:
|
|
103
|
-
typecheck:
|
|
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:
|
|
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 =
|
|
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 =
|
|
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
|
|
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
|
-
|
|
220
|
+
const controller = module[controllerName] as ControllerFunction;
|
|
221
|
+
return controller(...args);
|
|
248
222
|
};
|
|
249
223
|
}
|
|
250
224
|
|
|
251
|
-
export function registerRoutes(router
|
|
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
|
-
${
|
|
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 = {})
|
|
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:
|
|
558
|
-
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 =
|
|
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
|
-
|
|
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
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
|
|
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:
|
|
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(
|
|
629
|
+
console.log('\nInstalling dependencies with npm...\n');
|
|
683
630
|
|
|
684
631
|
try {
|
|
685
|
-
await installDependencies(targetDir
|
|
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(
|
|
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(
|
|
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.
|
|
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": "
|
|
22
|
+
"create-nativecore": "bin/index.mjs"
|
|
23
23
|
},
|
|
24
24
|
"files": [
|
|
25
25
|
"bin",
|