nuclie 1.0.2 → 1.0.4
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/dist/create-nuclie/templates.js +34 -37
- package/dist/dev/devServer.js +33 -9
- package/dist/init/bootstrap.d.ts +1 -1
- package/dist/init/bootstrap.js +19 -88
- package/package.json +1 -1
|
@@ -3,59 +3,56 @@
|
|
|
3
3
|
* Defines the file structure for 12 supported frameworks.
|
|
4
4
|
* Day 17: Create-Nuclie Templates Lock
|
|
5
5
|
*/
|
|
6
|
-
// Minimal definitions for the 12 frameworks to prove capability.
|
|
7
|
-
// In a real repo, these would be larger or loaded from external storage.
|
|
8
6
|
const COMMON_FILES = [
|
|
9
7
|
{
|
|
10
8
|
path: 'nuclie.config.ts',
|
|
11
|
-
content: `export default {
|
|
12
|
-
mode: 'development',
|
|
13
|
-
plugins: []
|
|
14
|
-
};`
|
|
9
|
+
content: `export default {\n mode: 'development',\n plugins: []\n};`
|
|
15
10
|
},
|
|
16
11
|
{
|
|
17
12
|
path: 'tsconfig.json',
|
|
18
|
-
content: `{
|
|
19
|
-
"compilerOptions": {
|
|
20
|
-
"target": "ESNext",
|
|
21
|
-
"module": "ESNext",
|
|
22
|
-
"moduleResolution": "bundler",
|
|
23
|
-
"strict": true,
|
|
24
|
-
"jsx": "preserve"
|
|
25
|
-
}
|
|
26
|
-
}`
|
|
13
|
+
content: `{\n "compilerOptions": {\n "target": "ESNext",\n "module": "ESNext",\n "moduleResolution": "bundler",\n "strict": true,\n "jsx": "preserve"\n }\n}`
|
|
27
14
|
}
|
|
28
15
|
];
|
|
29
16
|
export const TEMPLATES = {
|
|
30
17
|
'react-ts': {
|
|
31
18
|
id: 'react-ts',
|
|
32
19
|
name: 'React TypeScript',
|
|
33
|
-
description: 'React
|
|
20
|
+
description: 'React 19 + TypeScript + Modern Architecture + Premium UI',
|
|
34
21
|
files: [
|
|
35
22
|
...COMMON_FILES,
|
|
36
|
-
{ path: '
|
|
37
|
-
{ path: '
|
|
38
|
-
{ path: '
|
|
39
|
-
{ path: '
|
|
40
|
-
{ path: 'src/
|
|
23
|
+
{ path: '.gitignore', content: 'node_modules\ndist\n.DS_Store\n*.local\n.nuclie\n' },
|
|
24
|
+
{ path: 'README.md', content: '# Nuclie React App\n\nGenerated with Nuclie Forge.\n\n## Get started\n\n```bash\nnpm install\nnpm run dev\n```\n' },
|
|
25
|
+
{ path: 'index.html', content: `<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <link rel="icon" type="image/svg+xml" href="/favicon.svg" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <link rel="preconnect" href="https://fonts.googleapis.com">\n <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">\n <title>Nuclie Modern App</title>\n </head>\n <body>\n <div id="root"></div>\n <script type="module" src="/src/main.tsx"></script>\n </body>\n</html>` },
|
|
26
|
+
{ path: 'public/favicon.svg', content: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#00e5ff" opacity="0.3"/><circle cx="50" cy="50" r="20" fill="#00e5ff"/></svg>` },
|
|
27
|
+
{ path: 'src/main.tsx', content: `import { StrictMode } from 'react';\nimport { createRoot } from 'react-dom/client';\nimport './styles/global.css';\nimport App from './App.tsx';\n\nconst root = document.getElementById('root');\nif (root) {\n createRoot(root).render(\n <StrictMode>\n <App />\n </StrictMode>,\n );\n}` },
|
|
28
|
+
{ path: 'src/App.tsx', content: `import Landing from './pages/Landing';\n\nfunction App() {\n return (\n <div className="app-shell">\n <Landing />\n </div>\n );\n}\n\nexport default App;\n` },
|
|
29
|
+
{ path: 'src/pages/Landing.tsx', content: `import { useState } from 'react';\nimport Navbar from '../components/Navbar';\nimport Hero from '../components/Hero';\n\nexport default function Landing() {\n const [count, setCount] = useState(0);\n\n return (\n <div className="landing-page">\n <Navbar />\n <Hero count={count} onIncrement={() => setCount(c => c + 1)} />\n <footer className="footer">\n <p>© 2026 Nuclie Forge. Built with passion for developers.</p>\n </footer>\n </div>\n );\n}` },
|
|
30
|
+
{ path: 'src/components/Navbar.tsx', content: `import { useState, useEffect } from 'react';\n\nexport default function Navbar() {\n const [scrolled, setScrolled] = useState(false);\n\n useEffect(() => {\n const handleScroll = () => setScrolled(window.scrollY > 50);\n window.addEventListener('scroll', handleScroll);\n return () => window.removeEventListener('scroll', handleScroll);\n }, []);\n\n return (\n <nav className={scrolled ? 'nav scrolled' : 'nav'}>\n <div className="nav-content">\n <div className="logo-text">NUCLIE</div>\n <div className="nav-links">\n <a href="#features">Features</a>\n <a href="#docs">Docs</a>\n <button className="btn-primary-sm">Get Started</button>\n </div>\n </div>\n </nav>\n );\n}` },
|
|
31
|
+
{ path: 'src/components/Hero.tsx', content: `export default function Hero({ count, onIncrement }: { count: number, onIncrement: () => void }) {\n return (\n <main className="hero">\n <div className="hero-glow"></div>\n <div className="container">\n <div className="badge">Engine v1.0.3 Ready</div>\n <h1 className="hero-title">\n The Nucleus for <br />\n <span className="gradient-text">Stunning Web Apps</span>\n </h1>\n <p className="hero-subtitle">\n Experience the next generation of build speed with Nuclie. <br />\n Instant HMR, native performance, and a developer experience that feels like magic.\n </p>\n \n <div className="hero-actions">\n <button className="btn-primary" onClick={onIncrement}>\n Interactions: {count}\n </button>\n <button className="btn-secondary">Read Documentation</button>\n </div>\n\n <div className="hero-visual">\n <div className="glass-card">\n <div className="card-header">\n <div className="dot red"></div>\n <div className="dot yellow"></div>\n <div className="dot green"></div>\n </div>\n <code>\n <span className="comment">// Initializing the nucleus...</span><br />\n <span className="keyword">npm</span> install nuclie<br />\n <span className="keyword">npm</span> run dev<br />\n <br />\n <span className="success">✔ Core Ready in 3.15ms</span>\n </code>\n </div>\n </div>\n </div>\n </main>\n );\n}` },
|
|
32
|
+
{ path: 'src/styles/global.css', content: `:root {\n --primary: #00e5ff;\n --primary-glow: rgba(0, 229, 255, 0.4);\n --bg: #030712;\n --card-bg: rgba(255, 255, 255, 0.03);\n --card-border: rgba(255, 255, 255, 0.1);\n --text: #f9fafb;\n --text-muted: #9ca3af;\n \n font-family: 'Outfit', Inter, system-ui, sans-serif;\n background-color: var(--bg);\n color: var(--text);\n -webkit-font-smoothing: antialiased;\n}\n\n* { box-sizing: border-box; margin: 0; padding: 0; }\n\nbody { overflow-x: hidden; min-height: 100vh; }\n\n.container { max-width: 1100px; margin: 0 auto; padding: 0 2rem; }\n\n.nav { position: fixed; top: 0; left: 0; right: 0; z-index: 100; transition: 0.3s; padding: 1.5rem 0; }\n.nav.scrolled { background: rgba(3, 7, 18, 0.8); backdrop-filter: blur(12px); border-bottom: 1px solid var(--card-border); padding: 1rem 0; }\n.nav-content { max-width: 1100px; margin: 0 auto; padding: 0 2rem; display: flex; justify-content: space-between; align-items: center; }\n.logo-text { font-size: 1.5rem; font-weight: 800; letter-spacing: -1px; }\n.nav-links { display: flex; gap: 2rem; align-items: center; }\n.nav-links a { color: var(--text-muted); text-decoration: none; font-size: 0.9rem; transition: 0.2s; }\n.nav-links a:hover { color: var(--primary); }\n\n.gradient-text { background: linear-gradient(135deg, #fff 0%, var(--primary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\n\n.btn-primary { background: var(--primary); color: #000; border: none; padding: 0.8rem 2rem; border-radius: 99px; font-weight: 600; cursor: pointer; transition: 0.3s; box-shadow: 0 4px 20px var(--primary-glow); }\n.btn-primary-sm { background: var(--primary); color: #000; border: none; padding: 0.5rem 1.2rem; border-radius: 99px; font-weight: 600; cursor: pointer; font-size: 0.8rem; }\n.btn-secondary { background: transparent; color: #fff; border: 1px solid var(--card-border); padding: 0.8rem 2rem; border-radius: 99px; font-weight: 500; cursor: pointer; transition: 0.2s; }\n.btn-secondary:hover { background: rgba(255, 255, 255, 0.05); }\n\n.hero { padding: 10rem 0 5rem; position: relative; text-align: center; }\n.hero-glow { position: absolute; top: -200px; left: 50%; transform: translateX(-50%); width: 800px; height: 600px; background: radial-gradient(circle, var(--primary-glow) 0%, transparent 70%); filter: blur(80px); z-index: -1; opacity: 0.6; }\n.badge { display: inline-block; padding: 0.4rem 1rem; background: rgba(0, 229, 255, 0.1); border: 1px solid rgba(0, 229, 255, 0.2); border-radius: 30px; color: var(--primary); font-size: 0.8rem; font-weight: 600; margin-bottom: 2rem; }\n.hero-title { font-size: clamp(3rem, 8vw, 5rem); line-height: 1.1; letter-spacing: -2px; margin-bottom: 1.5rem; }\n.hero-subtitle { color: var(--text-muted); font-size: 1.2rem; line-height: 1.6; margin-bottom: 3rem; }\n.hero-actions { display: flex; gap: 1.5rem; justify-content: center; margin-bottom: 5rem; }\n\n.glass-card { background: var(--card-bg); backdrop-filter: blur(20px); border: 1px solid var(--card-border); padding: 1.5rem; border-radius: 12px; width: 100%; max-width: 600px; text-align: left; margin: 0 auto; }\n.card-header { display: flex; gap: 6px; margin-bottom: 1.5rem; }\n.dot { width: 10px; height: 10px; border-radius: 50%; }\n.dot.red { background: #ff5f56; }\n.dot.yellow { background: #ffbd2e; }\n.dot.green { background: #27c93f; }\n\ncode { font-family: 'Fira Code', monospace; font-size: 0.9rem; color: #d1d5db; }\n.keyword { color: #f472b6; }\n.comment { color: #6b7280; }\n.success { color: #34d399; }\n\n.footer { padding: 4rem 0; text-align: center; border-top: 1px solid var(--card-border); color: var(--text-muted); font-size: 0.9rem; }\n` },
|
|
33
|
+
{ path: 'src/utils/helpers.ts', content: `export const formatTime = (ms: number) => (ms / 1000).toFixed(2) + 's';\n` }
|
|
41
34
|
],
|
|
42
35
|
dependencies: { "react": "latest", "react-dom": "latest" },
|
|
43
36
|
devDependencies: { "@types/react": "latest", "@types/react-dom": "latest" }
|
|
44
37
|
},
|
|
45
|
-
vue: {
|
|
38
|
+
'vue': {
|
|
46
39
|
id: 'vue',
|
|
47
|
-
name: 'Vue',
|
|
48
|
-
description: 'Vue
|
|
40
|
+
name: 'Vue 3',
|
|
41
|
+
description: 'Vue 3 + TypeScript + Modern Architecture + Premium UI',
|
|
49
42
|
files: [
|
|
50
43
|
...COMMON_FILES,
|
|
51
|
-
{ path: '
|
|
52
|
-
{ path: '
|
|
53
|
-
{ path: '
|
|
44
|
+
{ path: '.gitignore', content: 'node_modules\ndist\n.DS_Store\n' },
|
|
45
|
+
{ path: 'index.html', content: `<!DOCTYPE html>\n<html lang="en">\n <head>\n <meta charset="UTF-8" />\n <link rel="icon" type="image/svg+xml" href="/favicon.svg" />\n <meta name="viewport" content="width=device-width, initial-scale=1.0" />\n <link rel="preconnect" href="https://fonts.googleapis.com">\n <link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>\n <link href="https://fonts.googleapis.com/css2?family=Outfit:wght@300;400;600;800&display=swap" rel="stylesheet">\n <title>Nuclie Modern Vue</title>\n </head>\n <body>\n <div id="app"></div>\n <script type="module" src="/src/main.ts"></script>\n </body>\n</html>` },
|
|
46
|
+
{ path: 'public/favicon.svg', content: `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><circle cx="50" cy="50" r="40" fill="#42b883" opacity="0.3"/><circle cx="50" cy="50" r="20" fill="#42b883"/></svg>` },
|
|
47
|
+
{ path: 'src/main.ts', content: `import { createApp } from 'vue';\nimport App from './App.vue';\nimport './styles/global.css';\n\ncreateApp(App).mount('#app');` },
|
|
48
|
+
{ path: 'src/App.vue', content: `<script setup lang="ts">\nimport Home from './pages/Home.vue';\n</script>\n<template>\n <div class="vue-app">\n <Home />\n </div>\n</template>` },
|
|
49
|
+
{ path: 'src/pages/Home.vue', content: `<script setup lang="ts">\nimport { ref } from 'vue';\nconst count = ref(0);\n</script>\n<template>\n <main class="hero">\n <div class="hero-glow"></div>\n <div class="container">\n <span class="badge">Vue Ready</span>\n <h1 class="hero-title">Nuclie x <span class="gradient-text">Vue 3</span></h1>\n <p class="hero-subtitle">The fastest Vue builds on the planet.</p>\n <button @click="count++" class="btn-primary">Interactions: {{ count }}</button>\n </div>\n </main>\n</template>` },
|
|
50
|
+
{ path: 'src/styles/global.css', content: `:root {\n --primary: #42b883;\n --primary-glow: rgba(66, 184, 131, 0.3);\n --bg: #030712;\n --text: #f9fafb;\n --text-muted: #9ca3af;\n font-family: 'Outfit', sans-serif;\n background-color: var(--bg);\n color: var(--text);\n}\n.container { max-width: 1100px; margin: 0 auto; padding: 0 2rem; }\n.gradient-text { background: linear-gradient(135deg, #fff 0%, var(--primary) 100%); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }\n.btn-primary { background: var(--primary); color: #000; border: none; padding: 0.8rem 2rem; border-radius: 99px; font-weight: 600; cursor: pointer; transition: 0.3s; }\n.btn-primary:hover { transform: translateY(-2px); box-shadow: 0 8px 30px var(--primary-glow); }\n.hero { padding: 10rem 0; text-align: center; position: relative; }\n.hero-glow { position: absolute; top: 0; left: 50%; transform: translateX(-50%); width: 600px; height: 400px; background: radial-gradient(circle, var(--primary-glow) 0%, transparent 70%); filter: blur(80px); z-index: -1; }\n.hero-title { font-size: clamp(3rem, 8vw, 5rem); margin-bottom: 2rem; }\n.hero-subtitle { color: var(--text-muted); font-size: 1.5rem; margin-bottom: 3rem; }\n.badge { display: inline-block; padding: 0.4rem 1rem; background: rgba(66, 184, 131, 0.1); border: 1px solid rgba(66, 184, 131, 0.2); border-radius: 30px; color: var(--primary); font-size: 0.8rem; margin-bottom: 2rem; }\n` }
|
|
54
51
|
],
|
|
55
52
|
dependencies: { "vue": "latest" },
|
|
56
53
|
devDependencies: { "@vue/compiler-sfc": "latest" }
|
|
57
54
|
},
|
|
58
|
-
svelte: {
|
|
55
|
+
'svelte': {
|
|
59
56
|
id: 'svelte',
|
|
60
57
|
name: 'Svelte',
|
|
61
58
|
description: 'Svelte Latest + TypeScript',
|
|
@@ -63,13 +60,13 @@ export const TEMPLATES = {
|
|
|
63
60
|
dependencies: { "svelte": "latest" },
|
|
64
61
|
devDependencies: {}
|
|
65
62
|
},
|
|
66
|
-
solid: { id: 'solid', name: 'Solid', description: 'SolidJS Signal Power', files: [...COMMON_FILES], dependencies: { "solid-js": "latest" }, devDependencies: {} },
|
|
67
|
-
lit: { id: 'lit', name: 'Lit', description: 'Web Components', files: [...COMMON_FILES], dependencies: { "lit": "latest" }, devDependencies: {} },
|
|
68
|
-
angular: { id: 'angular', name: 'Angular', description: 'Angular Ivy', files: [...COMMON_FILES], dependencies: { "@angular/core": "latest" }, devDependencies: {} },
|
|
69
|
-
preact: { id: 'preact', name: 'Preact', description: 'Fast 3kb React alt', files: [...COMMON_FILES], dependencies: { "preact": "latest" }, devDependencies: {} },
|
|
70
|
-
qwik: { id: 'qwik', name: 'Qwik', description: 'Resumable Framework', files: [...COMMON_FILES], dependencies: { "@builder.io/qwik": "latest" }, devDependencies: {} },
|
|
71
|
-
astro: { id: 'astro', name: 'Astro', description: 'Content Focused', files: [...COMMON_FILES], dependencies: { "astro": "latest" }, devDependencies: {} },
|
|
72
|
-
next: { id: 'next', name: 'Next-like', description: 'React SSR Router', files: [...COMMON_FILES], dependencies: { "react": "latest", "wouter": "latest" }, devDependencies: {} },
|
|
73
|
-
nuxt: { id: 'nuxt', name: 'Nuxt-like', description: 'Vue Meta Framework', files: [...COMMON_FILES], dependencies: { "vue": "latest", "vue-router": "latest" }, devDependencies: {} },
|
|
74
|
-
sveltekit: { id: 'sveltekit', name: 'SvelteKit-like', description: 'Svelte Fullstack', files: [...COMMON_FILES], dependencies: { "svelte": "latest" }, devDependencies: {} },
|
|
63
|
+
'solid': { id: 'solid', name: 'Solid', description: 'SolidJS Signal Power', files: [...COMMON_FILES], dependencies: { "solid-js": "latest" }, devDependencies: {} },
|
|
64
|
+
'lit': { id: 'lit', name: 'Lit', description: 'Web Components', files: [...COMMON_FILES], dependencies: { "lit": "latest" }, devDependencies: {} },
|
|
65
|
+
'angular': { id: 'angular', name: 'Angular', description: 'Angular Ivy', files: [...COMMON_FILES], dependencies: { "@angular/core": "latest" }, devDependencies: {} },
|
|
66
|
+
'preact': { id: 'preact', name: 'Preact', description: 'Fast 3kb React alt', files: [...COMMON_FILES], dependencies: { "preact": "latest" }, devDependencies: {} },
|
|
67
|
+
'qwik': { id: 'qwik', name: 'Qwik', description: 'Resumable Framework', files: [...COMMON_FILES], dependencies: { "@builder.io/qwik": "latest" }, devDependencies: {} },
|
|
68
|
+
'astro': { id: 'astro', name: 'Astro', description: 'Content Focused', files: [...COMMON_FILES], dependencies: { "astro": "latest" }, devDependencies: {} },
|
|
69
|
+
'next': { id: 'next', name: 'Next-like', description: 'React SSR Router', files: [...COMMON_FILES], dependencies: { "react": "latest", "wouter": "latest" }, devDependencies: {} },
|
|
70
|
+
'nuxt': { id: 'nuxt', name: 'Nuxt-like', description: 'Vue Meta Framework', files: [...COMMON_FILES], dependencies: { "vue": "latest", "vue-router": "latest" }, devDependencies: {} },
|
|
71
|
+
'sveltekit': { id: 'sveltekit', name: 'SvelteKit-like', description: 'Svelte Fullstack', files: [...COMMON_FILES], dependencies: { "svelte": "latest" }, devDependencies: {} },
|
|
75
72
|
};
|
package/dist/dev/devServer.js
CHANGED
|
@@ -655,15 +655,19 @@ export async function startDevServer(cliCfg, existingServer) {
|
|
|
655
655
|
}
|
|
656
656
|
}
|
|
657
657
|
try {
|
|
658
|
-
// Inject entry point if not present (
|
|
659
|
-
//
|
|
660
|
-
|
|
661
|
-
|
|
662
|
-
//
|
|
663
|
-
|
|
658
|
+
// Inject entry point if not present (heuristic)
|
|
659
|
+
// Check for any script tag pointing to entry files
|
|
660
|
+
const hasEntryScript = cfg.entry?.some(entry => {
|
|
661
|
+
const entryPath = entry.startsWith('/') ? entry : `/${entry}`;
|
|
662
|
+
// Match src="/src/main.tsx", src="src/main.tsx", src='src/main.tsx' etc.
|
|
663
|
+
const escaped = entryPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
|
|
664
|
+
const regex = new RegExp(`src=["']?\\.?${escaped}["']?`, 'i');
|
|
665
|
+
return regex.test(data);
|
|
666
|
+
});
|
|
667
|
+
if (!hasEntryScript) {
|
|
664
668
|
if (cfg.entry && cfg.entry.length > 0) {
|
|
665
669
|
const entryScript = `<script type="module" src="/${cfg.entry[0]}"></script>`;
|
|
666
|
-
data = data.replace('</body>',
|
|
670
|
+
data = data.replace('</body>', ` ${entryScript}\n</body>`);
|
|
667
671
|
}
|
|
668
672
|
}
|
|
669
673
|
// Inject only client runtime
|
|
@@ -797,7 +801,7 @@ export async function startDevServer(cliCfg, existingServer) {
|
|
|
797
801
|
}
|
|
798
802
|
return;
|
|
799
803
|
}
|
|
800
|
-
if (url === '/@nuclie/client') {
|
|
804
|
+
if (url === '/@nuclie/client' || url === '/@nexxo/client') {
|
|
801
805
|
const clientPath = path.resolve(__dirname, '../runtime/client.ts');
|
|
802
806
|
try {
|
|
803
807
|
const raw = await fs.readFile(clientPath, 'utf-8');
|
|
@@ -1148,12 +1152,32 @@ ${raw}
|
|
|
1148
1152
|
res.end(data);
|
|
1149
1153
|
}
|
|
1150
1154
|
catch (e) {
|
|
1155
|
+
if (e.code === 'ENOENT') {
|
|
1156
|
+
if (url === '/service-worker.js') {
|
|
1157
|
+
// Serve a dummy service worker that forcibly unregisters itself
|
|
1158
|
+
// This fixes issues when a browser has strongly cached an old SW
|
|
1159
|
+
res.writeHead(200, { 'Content-Type': 'application/javascript' });
|
|
1160
|
+
res.end(`
|
|
1161
|
+
self.addEventListener('install', () => self.skipWaiting());
|
|
1162
|
+
self.addEventListener('activate', (e) => {
|
|
1163
|
+
e.waitUntil(self.registration.unregister().then(() => self.clients.claim()));
|
|
1164
|
+
});
|
|
1165
|
+
`);
|
|
1166
|
+
return;
|
|
1167
|
+
}
|
|
1168
|
+
// Just return 404 cleanly without blasting the console for every missing map or img
|
|
1169
|
+
if (!res.headersSent) {
|
|
1170
|
+
res.writeHead(404);
|
|
1171
|
+
res.end('Not found');
|
|
1172
|
+
}
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1151
1175
|
log.error(`Request error: ${e.message}`, { category: 'server' });
|
|
1152
1176
|
if (process.env.DEBUG || process.env.NODE_ENV === 'test') {
|
|
1153
1177
|
console.error(e.stack);
|
|
1154
1178
|
}
|
|
1155
1179
|
else {
|
|
1156
|
-
// Always show stack for request errors in dev mode for visibility
|
|
1180
|
+
// Always show stack for actual request errors (not 404s) in dev mode for visibility
|
|
1157
1181
|
console.error(e);
|
|
1158
1182
|
}
|
|
1159
1183
|
const isJS = url.match(/\.(js|ts|tsx|jsx|mjs|vue|svelte|astro)/);
|
package/dist/init/bootstrap.d.ts
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
export declare function bootstrapProject(cwd: string,
|
|
1
|
+
export declare function bootstrapProject(cwd: string, templateId?: string): Promise<void>;
|
package/dist/init/bootstrap.js
CHANGED
|
@@ -1,12 +1,22 @@
|
|
|
1
1
|
import fs from 'fs/promises';
|
|
2
2
|
import path from 'path';
|
|
3
3
|
import { log } from '../utils/logger.js';
|
|
4
|
-
|
|
5
|
-
|
|
4
|
+
import { TEMPLATES } from '../create-nuclie/templates.js';
|
|
5
|
+
export async function bootstrapProject(cwd, templateId = 'react-ts') {
|
|
6
|
+
log.info(`Bootstrapping new ${templateId} project in ${cwd}`);
|
|
7
|
+
// Normalize template name
|
|
8
|
+
const tid = templateId === 'react' ? 'react-ts' : templateId;
|
|
9
|
+
const template = TEMPLATES[tid] || TEMPLATES['react-ts'];
|
|
6
10
|
// 1. Create directory structure
|
|
7
|
-
await fs.mkdir(
|
|
8
|
-
|
|
9
|
-
|
|
11
|
+
await fs.mkdir(cwd, { recursive: true });
|
|
12
|
+
// 2. Create Files from template
|
|
13
|
+
for (const file of template.files) {
|
|
14
|
+
const filePath = path.join(cwd, file.path);
|
|
15
|
+
const dir = path.dirname(filePath);
|
|
16
|
+
await fs.mkdir(dir, { recursive: true });
|
|
17
|
+
await fs.writeFile(filePath, file.content);
|
|
18
|
+
}
|
|
19
|
+
// 3. Create package.json
|
|
10
20
|
let nuclieVersion = 'latest';
|
|
11
21
|
try {
|
|
12
22
|
const localPkgPath = new URL('../../package.json', import.meta.url);
|
|
@@ -24,92 +34,13 @@ export async function bootstrapProject(cwd, template = 'react') {
|
|
|
24
34
|
"build": "nuclie build",
|
|
25
35
|
"preview": "nuclie dev --port 4173"
|
|
26
36
|
},
|
|
27
|
-
dependencies:
|
|
28
|
-
"react": "^19.2.3",
|
|
29
|
-
"react-dom": "^19.2.3"
|
|
30
|
-
} : {},
|
|
37
|
+
dependencies: template.dependencies,
|
|
31
38
|
devDependencies: {
|
|
32
|
-
|
|
33
|
-
"
|
|
34
|
-
"@types/react": "^19.0.0",
|
|
35
|
-
"@types/react-dom": "^19.0.0"
|
|
39
|
+
...template.devDependencies,
|
|
40
|
+
"nuclie": nuclieVersion
|
|
36
41
|
}
|
|
37
42
|
};
|
|
38
43
|
await fs.writeFile(path.join(cwd, 'package.json'), JSON.stringify(pkg, null, 2));
|
|
39
|
-
|
|
40
|
-
if (template === 'react' || template === 'react-ts') {
|
|
41
|
-
await fs.writeFile(path.join(cwd, 'src/main.tsx'), `
|
|
42
|
-
import React from 'react';
|
|
43
|
-
import ReactDOM from 'react-dom/client';
|
|
44
|
-
import App from './App';
|
|
45
|
-
import './index.css';
|
|
46
|
-
|
|
47
|
-
ReactDOM.createRoot(document.getElementById('root')!).render(
|
|
48
|
-
<React.StrictMode>
|
|
49
|
-
<App />
|
|
50
|
-
</React.StrictMode>
|
|
51
|
-
);
|
|
52
|
-
`.trim());
|
|
53
|
-
await fs.writeFile(path.join(cwd, 'src/App.tsx'), `
|
|
54
|
-
import React, { useState } from 'react';
|
|
55
|
-
|
|
56
|
-
function App() {
|
|
57
|
-
const [count, setCount] = useState(0);
|
|
58
|
-
|
|
59
|
-
return (
|
|
60
|
-
<div className="App">
|
|
61
|
-
<h1>Nuclie + React</h1>
|
|
62
|
-
<div className="card">
|
|
63
|
-
<button onClick={() => setCount((count) => count + 1)}>
|
|
64
|
-
count is {count}
|
|
65
|
-
</button>
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
|
|
71
|
-
export default App;
|
|
72
|
-
`.trim());
|
|
73
|
-
await fs.writeFile(path.join(cwd, 'src/index.css'), `
|
|
74
|
-
body {
|
|
75
|
-
margin: 0;
|
|
76
|
-
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
|
|
77
|
-
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
|
|
78
|
-
sans-serif;
|
|
79
|
-
-webkit-font-smoothing: antialiased;
|
|
80
|
-
-moz-osx-font-smoothing: grayscale;
|
|
81
|
-
background-color: #242424;
|
|
82
|
-
color: white;
|
|
83
|
-
display: flex;
|
|
84
|
-
place-items: center;
|
|
85
|
-
min-width: 320px;
|
|
86
|
-
min-height: 100vh;
|
|
87
|
-
}
|
|
88
|
-
`.trim());
|
|
89
|
-
}
|
|
90
|
-
// 4. Create index.html
|
|
91
|
-
await fs.writeFile(path.join(cwd, 'index.html'), `
|
|
92
|
-
<!DOCTYPE html>
|
|
93
|
-
<html lang="en">
|
|
94
|
-
<head>
|
|
95
|
-
<meta charset="UTF-8" />
|
|
96
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
97
|
-
<title>Nuclie App</title>
|
|
98
|
-
</head>
|
|
99
|
-
<body>
|
|
100
|
-
<div id="root"></div>
|
|
101
|
-
<script type="module" src="/src/main.tsx"></script>
|
|
102
|
-
</body>
|
|
103
|
-
</html>
|
|
104
|
-
`.trim());
|
|
105
|
-
// 5. Create nuclie.config.json
|
|
106
|
-
const config = {
|
|
107
|
-
entry: ["src/main.tsx"],
|
|
108
|
-
mode: "development",
|
|
109
|
-
preset: "spa",
|
|
110
|
-
platform: "browser"
|
|
111
|
-
};
|
|
112
|
-
await fs.writeFile(path.join(cwd, 'nuclie.config.json'), JSON.stringify(config, null, 2));
|
|
113
|
-
log.success(`Successfully bootstrapped ${template} project!`);
|
|
44
|
+
log.success(`Successfully bootstrapped ${template.name} in ${cwd}`);
|
|
114
45
|
log.info(`To get started:\n cd ${path.basename(cwd)}\n npm install\n npm run dev`);
|
|
115
46
|
}
|