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.
- package/README.md +10 -18
- package/bin/index.mjs +407 -489
- package/package.json +4 -3
- package/template/.env.example +28 -0
- package/template/.htmlhintrc +14 -0
- package/template/api/data/dashboard.json +11 -0
- package/template/api/data/users.json +18 -0
- package/template/api/mockApi.js +161 -0
- package/template/assets/icon.svg +13 -0
- package/template/assets/logo.svg +25 -0
- package/template/eslint.config.js +94 -0
- package/template/index.html +137 -0
- package/template/manifest.json +19 -0
- package/template/public/.well-known/security.txt +9 -0
- package/template/public/_headers +24 -0
- package/template/public/_redirects +14 -0
- package/template/public/assets/icon.svg +13 -0
- package/template/public/assets/logo.svg +25 -0
- package/template/public/manifest.json +19 -0
- package/template/public/robots.txt +13 -0
- package/template/public/sitemap.xml +27 -0
- package/template/scripts/build-for-bots.mjs +121 -0
- package/template/scripts/convert-to-ts.mjs +106 -0
- package/template/scripts/fix-encoding.mjs +38 -0
- package/template/scripts/fix-svg-paths.mjs +32 -0
- package/template/scripts/generate-cf-router.mjs +52 -0
- package/template/scripts/inject-dev-tools.mjs +41 -0
- package/template/scripts/inject-version.mjs +65 -0
- package/template/scripts/make-component.mjs +445 -0
- package/template/scripts/make-component.mjs.backup +432 -0
- package/template/scripts/make-controller.mjs +119 -0
- package/template/scripts/make-core-component.mjs +303 -0
- package/template/scripts/make-view.mjs +346 -0
- package/template/scripts/minify.mjs +71 -0
- package/template/scripts/prepare-static-assets.mjs +141 -0
- package/template/scripts/prompt-bot-build.mjs +223 -0
- package/template/scripts/remove-component.mjs +170 -0
- package/template/scripts/remove-core-component.mjs +156 -0
- package/template/scripts/remove-dev.mjs +13 -0
- package/template/scripts/remove-view.mjs +200 -0
- package/template/scripts/strip-dev-blocks.mjs +30 -0
- package/template/scripts/watch-compile.mjs +69 -0
- package/template/server.js +1066 -0
- package/template/src/app.ts +115 -0
- package/template/src/components/appRegistry.ts +8 -0
- package/template/src/components/core/app-footer.ts +27 -0
- package/template/src/components/core/app-header.ts +175 -0
- package/template/src/components/core/app-sidebar.ts +238 -0
- package/template/src/components/core/loading-spinner.ts +25 -0
- package/template/src/components/core/nc-a.ts +313 -0
- package/template/src/components/core/nc-accordion.ts +186 -0
- package/template/src/components/core/nc-alert.ts +153 -0
- package/template/src/components/core/nc-animation.ts +1150 -0
- package/template/src/components/core/nc-autocomplete.ts +271 -0
- package/template/src/components/core/nc-avatar-group.ts +113 -0
- package/template/src/components/core/nc-avatar.ts +148 -0
- package/template/src/components/core/nc-badge.ts +86 -0
- package/template/src/components/core/nc-bottom-nav.ts +214 -0
- package/template/src/components/core/nc-breadcrumb.ts +96 -0
- package/template/src/components/core/nc-button.ts +307 -0
- package/template/src/components/core/nc-card.ts +160 -0
- package/template/src/components/core/nc-checkbox.ts +282 -0
- package/template/src/components/core/nc-chip.ts +115 -0
- package/template/src/components/core/nc-code.ts +314 -0
- package/template/src/components/core/nc-collapsible.ts +154 -0
- package/template/src/components/core/nc-color-picker.ts +268 -0
- package/template/src/components/core/nc-copy-button.ts +119 -0
- package/template/src/components/core/nc-date-picker.ts +443 -0
- package/template/src/components/core/nc-div.ts +280 -0
- package/template/src/components/core/nc-divider.ts +81 -0
- package/template/src/components/core/nc-drawer.ts +230 -0
- package/template/src/components/core/nc-dropdown.ts +178 -0
- package/template/src/components/core/nc-empty-state.ts +134 -0
- package/template/src/components/core/nc-file-upload.ts +354 -0
- package/template/src/components/core/nc-form.ts +312 -0
- package/template/src/components/core/nc-image.ts +184 -0
- package/template/src/components/core/nc-input.ts +383 -0
- package/template/src/components/core/nc-kbd.ts +48 -0
- package/template/src/components/core/nc-menu-item.ts +193 -0
- package/template/src/components/core/nc-menu.ts +376 -0
- package/template/src/components/core/nc-modal.ts +238 -0
- package/template/src/components/core/nc-nav-item.ts +151 -0
- package/template/src/components/core/nc-number-input.ts +350 -0
- package/template/src/components/core/nc-otp-input.ts +235 -0
- package/template/src/components/core/nc-pagination.ts +178 -0
- package/template/src/components/core/nc-popover.ts +260 -0
- package/template/src/components/core/nc-progress-circular.ts +119 -0
- package/template/src/components/core/nc-progress.ts +134 -0
- package/template/src/components/core/nc-radio.ts +235 -0
- package/template/src/components/core/nc-rating.ts +266 -0
- package/template/src/components/core/nc-rich-text.ts +283 -0
- package/template/src/components/core/nc-scroll-top.ts +116 -0
- package/template/src/components/core/nc-select.ts +452 -0
- package/template/src/components/core/nc-skeleton.ts +107 -0
- package/template/src/components/core/nc-slider.ts +285 -0
- package/template/src/components/core/nc-snackbar.ts +230 -0
- package/template/src/components/core/nc-splash.ts +343 -0
- package/template/src/components/core/nc-stepper.ts +247 -0
- package/template/src/components/core/nc-switch.ts +281 -0
- package/template/src/components/core/nc-tab-item.ts +138 -0
- package/template/src/components/core/nc-table.ts +279 -0
- package/template/src/components/core/nc-tabs.ts +554 -0
- package/template/src/components/core/nc-tag-input.ts +279 -0
- package/template/src/components/core/nc-textarea.ts +216 -0
- package/template/src/components/core/nc-time-picker.ts +438 -0
- package/template/src/components/core/nc-timeline.ts +186 -0
- package/template/src/components/core/nc-tooltip.ts +143 -0
- package/template/src/components/frameworkRegistry.ts +68 -0
- package/template/src/components/preloadRegistry.ts +28 -0
- package/template/src/components/registry.ts +8 -0
- package/template/src/components/ui/dashboard-signal-lab.ts +284 -0
- package/template/src/constants/apiEndpoints.ts +27 -0
- package/template/src/constants/errorMessages.ts +23 -0
- package/template/src/constants/index.ts +8 -0
- package/template/src/constants/routePaths.ts +15 -0
- package/template/src/constants/storageKeys.ts +18 -0
- package/template/src/controllers/dashboard.controller.ts +200 -0
- package/template/src/controllers/home.controller.ts +21 -0
- package/template/src/controllers/index.ts +11 -0
- package/template/src/controllers/login.controller.ts +131 -0
- package/template/src/core/component.ts +354 -0
- package/template/src/core/errorHandler.ts +85 -0
- package/template/src/core/gpu-animation.ts +604 -0
- package/template/src/core/http.ts +173 -0
- package/template/src/core/lazyComponents.ts +90 -0
- package/template/src/core/router.ts +642 -0
- package/template/src/core/signals.ts +146 -0
- package/template/src/core/state.ts +248 -0
- package/template/src/dev/component-editor.ts +1363 -0
- package/template/src/dev/component-overlay.ts +278 -0
- package/template/src/dev/context-menu.ts +223 -0
- package/template/src/dev/denc-tools.ts +250 -0
- package/template/src/dev/hmr.ts +189 -0
- package/template/src/dev/nfbs.code-workspace +27 -0
- package/template/src/dev/outline-panel.ts +1247 -0
- package/template/src/middleware/auth.middleware.ts +23 -0
- package/template/src/routes/routes.ts +38 -0
- package/template/src/services/api.service.ts +394 -0
- package/template/src/services/auth.service.ts +176 -0
- package/template/src/services/index.ts +8 -0
- package/template/src/services/logger.service.ts +74 -0
- package/template/src/services/storage.service.ts +88 -0
- package/template/src/stores/appStore.ts +57 -0
- package/template/src/stores/uiStore.ts +36 -0
- package/template/src/styles/core-variables.css +219 -0
- package/template/src/styles/core.css +710 -0
- package/template/src/styles/main.css +3164 -0
- package/template/src/styles/variables.css +152 -0
- package/template/src/types/global.d.ts +47 -0
- package/template/src/utils/cacheBuster.ts +20 -0
- package/template/src/utils/dom.ts +149 -0
- package/template/src/utils/events.ts +203 -0
- package/template/src/utils/form.ts +176 -0
- package/template/src/utils/formatters.ts +169 -0
- package/template/src/utils/helpers.ts +195 -0
- package/template/src/utils/markdown.ts +307 -0
- package/template/src/utils/sidebar.ts +96 -0
- package/template/src/utils/smoothScroll.ts +85 -0
- package/template/src/utils/templates.ts +23 -0
- package/template/src/utils/validation.ts +73 -0
- package/template/src/views/protected/dashboard.html +293 -0
- package/template/src/views/public/home.html +150 -0
- package/template/src/views/public/login.html +102 -0
- package/template/tests/unit/component.test.ts +87 -0
- package/template/tests/unit/computed.test.ts +79 -0
- package/template/tests/unit/form.test.ts +68 -0
- package/template/tests/unit/formatters.test.ts +49 -0
- package/template/tests/unit/lazy-components.test.ts +59 -0
- package/template/tests/unit/markdown.test.ts +62 -0
- package/template/tests/unit/router.test.ts +112 -0
- package/template/tests/unit/signals.test.ts +54 -0
- package/template/tests/unit/validation.test.ts +50 -0
- package/template/tsconfig.build.json +21 -0
- package/template/tsconfig.json +51 -0
- package/template/vitest.config.ts +36 -0
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
|
|
69
|
-
|
|
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
|
|
77
|
-
const
|
|
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(
|
|
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(
|
|
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
|
-
|
|
91
|
+
description: `${config.projectTitle} built with NativeCore`,
|
|
114
92
|
type: 'module',
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
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
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
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
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
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/
|
|
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/
|
|
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
|
|
245
|
-
|
|
246
|
-
|
|
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
|
|
178
|
+
export function registerRoutes(router: Router): void {
|
|
252
179
|
router
|
|
253
|
-
.register('/', 'src/views/
|
|
254
|
-
${
|
|
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
|
|
260
|
-
const
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
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
|
-
|
|
297
|
-
|
|
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
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
203
|
+
auth.setUser(response.user);
|
|
204
|
+
} catch {
|
|
205
|
+
auth.logout();
|
|
206
|
+
}
|
|
304
207
|
}
|
|
305
208
|
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
const
|
|
309
|
-
const
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
|
|
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
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
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
|
|
333
|
-
|
|
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
|
-
|
|
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
|
-
|
|
369
|
-
|
|
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
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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
|
-
|
|
397
|
-
|
|
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
|
-
|
|
407
|
-
|
|
408
|
-
|
|
409
|
-
|
|
410
|
-
margin: 0 auto;
|
|
411
|
-
padding: 4rem 0 5rem;
|
|
412
|
-
}
|
|
277
|
+
function initDevTools(): void {
|
|
278
|
+
if (!isLocalhost()) {
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
413
281
|
|
|
414
|
-
.
|
|
415
|
-
.
|
|
416
|
-
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
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
|
-
|
|
424
|
-
|
|
294
|
+
init();
|
|
295
|
+
`;
|
|
425
296
|
}
|
|
426
297
|
|
|
427
|
-
|
|
428
|
-
|
|
429
|
-
|
|
430
|
-
|
|
431
|
-
|
|
432
|
-
|
|
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
|
-
|
|
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
|
-
|
|
448
|
-
.
|
|
449
|
-
|
|
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
|
-
|
|
456
|
-
|
|
457
|
-
|
|
458
|
-
|
|
459
|
-
|
|
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
|
-
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
|
|
466
|
-
|
|
467
|
-
|
|
468
|
-
|
|
469
|
-
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
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
|
-
|
|
476
|
-
|
|
477
|
-
|
|
478
|
-
|
|
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
|
-
|
|
482
|
-
|
|
483
|
-
}
|
|
482
|
+
if (config.includeAuth) {
|
|
483
|
+
lines.push("export { loginController } from './login.controller.js';");
|
|
484
|
+
}
|
|
484
485
|
|
|
485
|
-
|
|
486
|
-
|
|
487
|
-
|
|
488
|
-
border-color: var(--border);
|
|
489
|
-
}
|
|
486
|
+
if (config.includeDashboard) {
|
|
487
|
+
lines.push("export { dashboardController } from './dashboard.controller.js';");
|
|
488
|
+
}
|
|
490
489
|
|
|
491
|
-
|
|
492
|
-
padding: 2rem;
|
|
490
|
+
return `${lines.join('\n')}\n`;
|
|
493
491
|
}
|
|
494
492
|
|
|
495
|
-
|
|
496
|
-
|
|
493
|
+
async function copyTemplate(targetDir) {
|
|
494
|
+
await fs.cp(templateDir, targetDir, { recursive: true, force: true });
|
|
497
495
|
}
|
|
498
496
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
|
|
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
|
-
.
|
|
505
|
-
|
|
506
|
-
|
|
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
|
-
.
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
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
|
-
.
|
|
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
|
-
.
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
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
|
-
.
|
|
535
|
-
|
|
536
|
-
|
|
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
|
-
|
|
540
|
-
|
|
541
|
-
|
|
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
|
-
.
|
|
546
|
-
|
|
547
|
-
|
|
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
|
-
|
|
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
|
|
582
|
-
await
|
|
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
|
|
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(
|
|
600
|
+
console.log('\nInstalling dependencies with npm...\n');
|
|
683
601
|
|
|
684
602
|
try {
|
|
685
|
-
await installDependencies(targetDir
|
|
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(
|
|
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(
|
|
707
|
-
console.log('\nThis
|
|
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
|
+
});
|