nasse-js 1.0.0 → 1.0.2
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 +9 -9
- package/nasse.config.mjs +9 -0
- package/package.json +8 -6
- package/src/app/_layout.tsx +52 -0
- package/src/app/index.tsx +135 -0
- package/src/cli/create.ts +369 -0
- package/src/cli/nasse.ts +116 -0
- package/dist/cli/create.js +0 -335
- package/dist/cli/ozone.js +0 -79
- package/ozone.config.mjs +0 -9
package/README.md
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
|
-
# ⚡
|
|
1
|
+
# ⚡ Nasse JS
|
|
2
2
|
|
|
3
3
|
**The React Framework for Humans.**
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
Nasse JS es un framework de React moderno, intuitivo y extremadamente rápido, diseñado para ofrecer la potencia de Next.js con una fracción de la complejidad. Cero configuración, máximo rendimiento.
|
|
6
6
|
|
|
7
7
|
## ✨ Características Principales
|
|
8
8
|
|
|
@@ -14,10 +14,10 @@ Ozone JS es un framework de React moderno, intuitivo y extremadamente rápido, d
|
|
|
14
14
|
|
|
15
15
|
## 🚀 Inicio Rápido
|
|
16
16
|
|
|
17
|
-
Para crear un nuevo proyecto de
|
|
17
|
+
Para crear un nuevo proyecto de Nasse JS:
|
|
18
18
|
|
|
19
19
|
```bash
|
|
20
|
-
npx create-
|
|
20
|
+
npx create-nasse-app mi-proyecto
|
|
21
21
|
cd mi-proyecto
|
|
22
22
|
npm install
|
|
23
23
|
npm run dev
|
|
@@ -32,7 +32,7 @@ mi-proyecto/
|
|
|
32
32
|
│ ├── _layout.tsx # Layout raíz (compartido)
|
|
33
33
|
│ └── index.tsx # Página de inicio (/)
|
|
34
34
|
├── public/ # Assets estáticos (logos, icons)
|
|
35
|
-
├──
|
|
35
|
+
├── nasse.config.mjs # Configuración del framework
|
|
36
36
|
└── package.json
|
|
37
37
|
```
|
|
38
38
|
|
|
@@ -66,10 +66,10 @@ export default function Home({ message }) {
|
|
|
66
66
|
|
|
67
67
|
## 💻 Comandos del CLI
|
|
68
68
|
|
|
69
|
-
- `
|
|
70
|
-
- `
|
|
71
|
-
- `
|
|
69
|
+
- `nasse dev`: Inicia el servidor de desarrollo con HMR.
|
|
70
|
+
- `nasse build`: Prepara la aplicación para producción (Próximamente).
|
|
71
|
+
- `nasse start`: Levanta el servidor de producción.
|
|
72
72
|
|
|
73
73
|
---
|
|
74
74
|
|
|
75
|
-
Diseñado con ❤️ para millones de desarrolladores. **
|
|
75
|
+
Diseñado con ❤️ para millones de desarrolladores. **Nasse JS** es el futuro de la web simple.
|
package/nasse.config.mjs
ADDED
package/package.json
CHANGED
|
@@ -1,21 +1,23 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "nasse-js",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "The React framework for humans",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
7
|
-
"create-
|
|
8
|
-
"
|
|
7
|
+
"create-nasse-app": "./dist/cli/create.js",
|
|
8
|
+
"nasse": "./dist/cli/nasse.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"dist",
|
|
12
|
+
"src",
|
|
12
13
|
"public",
|
|
13
14
|
"package.json",
|
|
14
15
|
"README.md",
|
|
15
|
-
"
|
|
16
|
+
"nasse.config.mjs"
|
|
16
17
|
],
|
|
17
18
|
"scripts": {
|
|
18
|
-
"build": "tsc"
|
|
19
|
+
"build": "tsc",
|
|
20
|
+
"prepublishOnly": "npm run build"
|
|
19
21
|
},
|
|
20
22
|
"dependencies": {
|
|
21
23
|
"chalk": "^5.3.0",
|
|
@@ -38,4 +40,4 @@
|
|
|
38
40
|
"@types/react-dom": "^18.2.0",
|
|
39
41
|
"typescript": "^5.3.0"
|
|
40
42
|
}
|
|
41
|
-
}
|
|
43
|
+
}
|
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* RootLayout defines the HTML shell for all pages.
|
|
5
|
+
* Optimized for performance and SEO.
|
|
6
|
+
*/
|
|
7
|
+
export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
8
|
+
return (
|
|
9
|
+
<html lang="es">
|
|
10
|
+
<head>
|
|
11
|
+
<meta charSet="utf-8" />
|
|
12
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
13
|
+
{/* SVG Favicon - Modern and Lightweight */}
|
|
14
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
15
|
+
|
|
16
|
+
{/* Resource Preloading */}
|
|
17
|
+
<link rel="preconnect" href="https://fonts.googleapis.com" />
|
|
18
|
+
<link rel="preconnect" href="https://fonts.gstatic.com" crossOrigin="anonymous" />
|
|
19
|
+
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;600;700;800&display=swap" rel="stylesheet" />
|
|
20
|
+
|
|
21
|
+
<style>{`
|
|
22
|
+
:root {
|
|
23
|
+
--background: #030303;
|
|
24
|
+
--foreground: #ffffff;
|
|
25
|
+
--accent: #8b5cf6;
|
|
26
|
+
--accent-glow: rgba(139, 92, 246, 0.15);
|
|
27
|
+
}
|
|
28
|
+
body {
|
|
29
|
+
margin: 0;
|
|
30
|
+
padding: 0;
|
|
31
|
+
background-color: var(--background);
|
|
32
|
+
color: var(--foreground);
|
|
33
|
+
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
34
|
+
-webkit-font-smoothing: antialiased;
|
|
35
|
+
overflow-x: hidden;
|
|
36
|
+
line-height: 1.5;
|
|
37
|
+
}
|
|
38
|
+
* { box-sizing: border-box; }
|
|
39
|
+
|
|
40
|
+
/* Optimization: Smooth Transitions */
|
|
41
|
+
.nasse-page-enter { opacity: 0; transform: translateY(10px); }
|
|
42
|
+
.nasse-page-enter-active { opacity: 1; transform: translateY(0); transition: opacity 300ms, transform 300ms; }
|
|
43
|
+
`}</style>
|
|
44
|
+
</head>
|
|
45
|
+
<body>
|
|
46
|
+
<div id="nasse-root">
|
|
47
|
+
{children}
|
|
48
|
+
</div>
|
|
49
|
+
</body>
|
|
50
|
+
</html>
|
|
51
|
+
);
|
|
52
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Metadata for SEO.
|
|
5
|
+
* Nasse JS reads this automatically for server-side meta injection.
|
|
6
|
+
*/
|
|
7
|
+
export const meta = {
|
|
8
|
+
title: "Nasse JS | El Framework para Humanos",
|
|
9
|
+
description: "Desarrolla aplicaciones ultra-rápidas con la simplicidad que mereces."
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
/**
|
|
13
|
+
* server() runs only on the backend.
|
|
14
|
+
* Data returned here is injected as props into the Home component.
|
|
15
|
+
*/
|
|
16
|
+
export const server = async () => {
|
|
17
|
+
// Simulating database fetch
|
|
18
|
+
return {
|
|
19
|
+
version: "1.0.0",
|
|
20
|
+
status: "Operativo",
|
|
21
|
+
stats: {
|
|
22
|
+
loadTime: "0.4ms",
|
|
23
|
+
bundleSize: "12kb"
|
|
24
|
+
}
|
|
25
|
+
};
|
|
26
|
+
};
|
|
27
|
+
|
|
28
|
+
interface HomeProps {
|
|
29
|
+
version: string;
|
|
30
|
+
status: string;
|
|
31
|
+
stats: {
|
|
32
|
+
loadTime: string;
|
|
33
|
+
bundleSize: string;
|
|
34
|
+
};
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
export default function Home({ version, status, stats }: HomeProps) {
|
|
38
|
+
return (
|
|
39
|
+
<main style={{
|
|
40
|
+
minHeight: '100vh',
|
|
41
|
+
display: 'flex',
|
|
42
|
+
flexDirection: 'column',
|
|
43
|
+
alignItems: 'center',
|
|
44
|
+
justifyContent: 'center',
|
|
45
|
+
padding: '2rem',
|
|
46
|
+
textAlign: 'center',
|
|
47
|
+
position: 'relative'
|
|
48
|
+
}}>
|
|
49
|
+
{/* Background Glow */}
|
|
50
|
+
<div style={{
|
|
51
|
+
position: 'absolute',
|
|
52
|
+
top: '20%',
|
|
53
|
+
left: '50%',
|
|
54
|
+
transform: 'translate(-50%, -50%)',
|
|
55
|
+
width: '400px',
|
|
56
|
+
height: '400px',
|
|
57
|
+
backgroundColor: 'var(--accent)',
|
|
58
|
+
filter: 'blur(120px)',
|
|
59
|
+
opacity: '0.15',
|
|
60
|
+
zIndex: -1,
|
|
61
|
+
borderRadius: '50%'
|
|
62
|
+
}} />
|
|
63
|
+
|
|
64
|
+
<header style={{ marginBottom: '3rem' }}>
|
|
65
|
+
<img src="/logo.svg" alt="Nasse Logo" style={{ width: '120px', height: '120px', marginBottom: '1.5rem' }} />
|
|
66
|
+
<h1 style={{
|
|
67
|
+
fontSize: '4rem',
|
|
68
|
+
fontWeight: 800,
|
|
69
|
+
margin: 0,
|
|
70
|
+
background: 'linear-gradient(to bottom, #fff, #a1a1aa)',
|
|
71
|
+
WebkitBackgroundClip: 'text',
|
|
72
|
+
WebkitTextFillColor: 'transparent',
|
|
73
|
+
letterSpacing: '-0.05em'
|
|
74
|
+
}}>
|
|
75
|
+
Nasse JS
|
|
76
|
+
</h1>
|
|
77
|
+
<p style={{
|
|
78
|
+
fontSize: '1.25rem',
|
|
79
|
+
color: '#a1a1aa',
|
|
80
|
+
maxWidth: '500px',
|
|
81
|
+
margin: '1rem auto 0',
|
|
82
|
+
lineHeight: 1.6
|
|
83
|
+
}}>
|
|
84
|
+
La alternativa intuitiva a Next.js. Menos configuración, más creación.
|
|
85
|
+
</p>
|
|
86
|
+
</header>
|
|
87
|
+
|
|
88
|
+
<section style={{
|
|
89
|
+
display: 'grid',
|
|
90
|
+
gridTemplateColumns: 'repeat(auto-fit, minmax(200px, 1fr))',
|
|
91
|
+
gap: '1.5rem',
|
|
92
|
+
width: '100%',
|
|
93
|
+
maxWidth: '800px',
|
|
94
|
+
marginTop: '2rem'
|
|
95
|
+
}}>
|
|
96
|
+
<div className="card" style={{
|
|
97
|
+
background: 'rgba(255, 255, 255, 0.03)',
|
|
98
|
+
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
99
|
+
padding: '1.5rem',
|
|
100
|
+
borderRadius: '16px',
|
|
101
|
+
backdropFilter: 'blur(10px)'
|
|
102
|
+
}}>
|
|
103
|
+
<h3 style={{ color: 'var(--accent)', margin: '0 0 0.5rem 0', fontSize: '0.9rem', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Versión</h3>
|
|
104
|
+
<p style={{ fontSize: '1.5rem', fontWeight: 600, margin: 0 }}>v{version}</p>
|
|
105
|
+
</div>
|
|
106
|
+
|
|
107
|
+
<div className="card" style={{
|
|
108
|
+
background: 'rgba(255, 255, 255, 0.03)',
|
|
109
|
+
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
110
|
+
padding: '1.5rem',
|
|
111
|
+
borderRadius: '16px',
|
|
112
|
+
backdropFilter: 'blur(10px)'
|
|
113
|
+
}}>
|
|
114
|
+
<h3 style={{ color: 'var(--accent)', margin: '0 0 0.5rem 0', fontSize: '0.9rem', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Estado</h3>
|
|
115
|
+
<p style={{ fontSize: '1.5rem', fontWeight: 600, margin: 0 }}>{status}</p>
|
|
116
|
+
</div>
|
|
117
|
+
|
|
118
|
+
<div className="card" style={{
|
|
119
|
+
background: 'rgba(255, 255, 255, 0.03)',
|
|
120
|
+
border: '1px solid rgba(255, 255, 255, 0.08)',
|
|
121
|
+
padding: '1.5rem',
|
|
122
|
+
borderRadius: '16px',
|
|
123
|
+
backdropFilter: 'blur(10px)'
|
|
124
|
+
}}>
|
|
125
|
+
<h3 style={{ color: 'var(--accent)', margin: '0 0 0.5rem 0', fontSize: '0.9rem', textTransform: 'uppercase', letterSpacing: '0.1em' }}>Latencia SSR</h3>
|
|
126
|
+
<p style={{ fontSize: '1.5rem', fontWeight: 600, margin: 0 }}>{stats.loadTime}</p>
|
|
127
|
+
</div>
|
|
128
|
+
</section>
|
|
129
|
+
|
|
130
|
+
<footer style={{ marginTop: '4rem', color: '#52525b', fontSize: '0.9rem' }}>
|
|
131
|
+
Edita <code>src/app/index.tsx</code> para comenzar la aventura.
|
|
132
|
+
</footer>
|
|
133
|
+
</main>
|
|
134
|
+
);
|
|
135
|
+
}
|
|
@@ -0,0 +1,369 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import fs from 'fs';
|
|
4
|
+
import path from 'path';
|
|
5
|
+
import { fileURLToPath } from 'url';
|
|
6
|
+
import chalk from 'chalk';
|
|
7
|
+
import inquirer from 'inquirer';
|
|
8
|
+
import { execSync } from 'child_process';
|
|
9
|
+
|
|
10
|
+
const __filename = fileURLToPath(import.meta.url);
|
|
11
|
+
const __dirname = path.dirname(__filename);
|
|
12
|
+
|
|
13
|
+
export async function init() {
|
|
14
|
+
console.log(`\n ${chalk.bold.hex('#8b5cf6')('⚡')} ${chalk.bold('NASSE JS')} - The React Framework for Humans\n`);
|
|
15
|
+
|
|
16
|
+
const answers = await inquirer.prompt([
|
|
17
|
+
{
|
|
18
|
+
type: 'input',
|
|
19
|
+
name: 'projectName',
|
|
20
|
+
message: 'What is your project named?',
|
|
21
|
+
default: 'my-nasse-app',
|
|
22
|
+
validate: (input: string) => {
|
|
23
|
+
if (/^([A-Za-z\-\_\d])+$/.test(input)) return true;
|
|
24
|
+
return 'Project name may only include letters, numbers, underscores and dashes.';
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
{
|
|
28
|
+
type: 'confirm',
|
|
29
|
+
name: 'useTailwind',
|
|
30
|
+
message: 'Would you like to use Tailwind CSS?',
|
|
31
|
+
default: true
|
|
32
|
+
}
|
|
33
|
+
]);
|
|
34
|
+
|
|
35
|
+
const targetDir = path.join(process.cwd(), answers.projectName);
|
|
36
|
+
|
|
37
|
+
if (fs.existsSync(targetDir)) {
|
|
38
|
+
console.error(chalk.red(`\nDirectory ${answers.projectName} already exists. Please choose another name.\n`));
|
|
39
|
+
process.exit(1);
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
console.log(`\nCreating a new Nasse app in ${chalk.green(targetDir)}.`);
|
|
43
|
+
fs.mkdirSync(targetDir, { recursive: true });
|
|
44
|
+
|
|
45
|
+
// Create standard folders
|
|
46
|
+
fs.mkdirSync(path.join(targetDir, 'app'), { recursive: true });
|
|
47
|
+
fs.mkdirSync(path.join(targetDir, 'public'), { recursive: true });
|
|
48
|
+
fs.mkdirSync(path.join(targetDir, 'components'), { recursive: true });
|
|
49
|
+
fs.mkdirSync(path.join(targetDir, 'assets'), { recursive: true });
|
|
50
|
+
|
|
51
|
+
// --- 1. GENERATE APP FILES ---
|
|
52
|
+
|
|
53
|
+
// app/index.tsx
|
|
54
|
+
fs.writeFileSync(path.join(targetDir, 'app', 'index.tsx'), `import React from 'react';
|
|
55
|
+
|
|
56
|
+
export const meta = {
|
|
57
|
+
title: "Welcome to Nasse JS",
|
|
58
|
+
description: "The React framework for humans."
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const server = async () => {
|
|
62
|
+
return {
|
|
63
|
+
message: "Server-side loaded data!"
|
|
64
|
+
};
|
|
65
|
+
};
|
|
66
|
+
|
|
67
|
+
export default function Home({ message }: { message: string }) {
|
|
68
|
+
return (
|
|
69
|
+
<div ${answers.useTailwind ? 'className="min-h-screen bg-gray-950 text-white flex flex-col items-center justify-center p-8"' : 'style={{ minHeight: "100vh", background: "#09090b", color: "white", padding: "2rem", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", fontFamily: "system-ui" }}'}>
|
|
70
|
+
<img src="/logo.svg" alt="Nasse Logo" ${answers.useTailwind ? 'className="w-24 h-24 mb-6"' : 'style={{ width: "96px", marginBottom: "1.5rem" }}'} />
|
|
71
|
+
<h1 ${answers.useTailwind ? 'className="text-4xl font-bold text-violet-500 mb-4"' : 'style={{ fontSize: "2.5rem", color: "#8b5cf6", margin: "0 0 1rem 0" }}'}>⚡ Nasse JS</h1>
|
|
72
|
+
<p ${answers.useTailwind ? 'className="text-gray-400 mb-8"' : 'style={{ color: "#a1a1aa", marginBottom: "2rem" }}'}>Welcome to your new app. Edit <code>app/index.tsx</code> to get started.</p>
|
|
73
|
+
<div ${answers.useTailwind ? 'className="p-4 bg-gray-900 rounded-lg border border-gray-800"' : 'style={{ padding: "1rem", background: "#18181b", borderRadius: "8px", border: "1px solid #27272a" }}'}>
|
|
74
|
+
<strong ${answers.useTailwind ? 'className="text-violet-400"' : 'style={{ color: "#a78bfa" }}'}>Data from server:</strong> {message}
|
|
75
|
+
</div>
|
|
76
|
+
</div>
|
|
77
|
+
);
|
|
78
|
+
}
|
|
79
|
+
`);
|
|
80
|
+
|
|
81
|
+
// app/_layout.tsx
|
|
82
|
+
const globalCssImport = answers.useTailwind ? `import '../assets/global.css';\n\n` : '';
|
|
83
|
+
fs.writeFileSync(path.join(targetDir, 'app', '_layout.tsx'), `import React from 'react';\n${globalCssImport}export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
84
|
+
return (
|
|
85
|
+
<html lang="en">
|
|
86
|
+
<head>
|
|
87
|
+
<meta charSet="utf-8" />
|
|
88
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
89
|
+
<link rel="icon" href="/favicon.ico" />
|
|
90
|
+
</head>
|
|
91
|
+
<body ${!answers.useTailwind ? 'style={{ margin: 0 }}' : ''}>
|
|
92
|
+
{children}
|
|
93
|
+
</body>
|
|
94
|
+
</html>
|
|
95
|
+
);
|
|
96
|
+
}
|
|
97
|
+
`);
|
|
98
|
+
|
|
99
|
+
// --- 2. GENERATE PUBLIC ASSETS ---
|
|
100
|
+
|
|
101
|
+
// Generate high-quality original SVGs
|
|
102
|
+
fs.writeFileSync(path.join(targetDir, 'public', 'favicon.svg'), `<svg width="32" height="32" viewBox="0 0 32 32" fill="none" xmlns="http://www.w3.org/2000/svg"><path d="M18 2L8 18H14L11 30L24 13H18L21 2Z" fill="#8b5cf6"/></svg>`);
|
|
103
|
+
fs.writeFileSync(path.join(targetDir, 'public', 'logo.svg'), `<svg width="100" height="100" viewBox="0 0 100 100" fill="none" xmlns="http://www.w3.org/2000/svg"><defs><linearGradient id="boltGradient" x1="0%" y1="0%" x2="100%" y2="100%"><stop offset="0%" stop-color="#a78bfa" /><stop offset="100%" stop-color="#8b5cf6" /></linearGradient></defs><path d="M55 5L25 55H45L35 95L75 40H55L65 5Z" fill="url(#boltGradient)"/></svg>`);
|
|
104
|
+
fs.writeFileSync(path.join(targetDir, 'public', 'globe.svg'), `<svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#52525b" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" xmlns="http://www.w3.org/2000/svg"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/><path d="M2 12h20"/></svg>`);
|
|
105
|
+
|
|
106
|
+
// --- 3. GENERATE ROOT CONFIG FILES ---
|
|
107
|
+
|
|
108
|
+
// .gitignore (100% git compatible)
|
|
109
|
+
fs.writeFileSync(path.join(targetDir, '.gitignore'), `# See https://help.github.com/articles/ignoring-files/
|
|
110
|
+
|
|
111
|
+
# dependencies
|
|
112
|
+
/node_modules
|
|
113
|
+
/.pnp
|
|
114
|
+
.pnp.js
|
|
115
|
+
|
|
116
|
+
# testing
|
|
117
|
+
/coverage
|
|
118
|
+
|
|
119
|
+
# nasse
|
|
120
|
+
.nasse
|
|
121
|
+
/dist
|
|
122
|
+
/build
|
|
123
|
+
|
|
124
|
+
# misc
|
|
125
|
+
.DS_Store
|
|
126
|
+
*.pem
|
|
127
|
+
|
|
128
|
+
# debug
|
|
129
|
+
npm-debug.log*
|
|
130
|
+
yarn-debug.log*
|
|
131
|
+
yarn-error.log*
|
|
132
|
+
|
|
133
|
+
# local env files
|
|
134
|
+
.env.local
|
|
135
|
+
.env.development.local
|
|
136
|
+
.env.test.local
|
|
137
|
+
.env.production.local
|
|
138
|
+
`);
|
|
139
|
+
|
|
140
|
+
// .env.local
|
|
141
|
+
fs.writeFileSync(path.join(targetDir, '.env.local'), `# Local environment variables
|
|
142
|
+
NASSE_PUBLIC_API_URL=http://localhost:3000/api
|
|
143
|
+
`);
|
|
144
|
+
|
|
145
|
+
// AGENTS.md
|
|
146
|
+
fs.writeFileSync(path.join(targetDir, 'AGENTS.md'), `# Project Agents Guidelines
|
|
147
|
+
|
|
148
|
+
This document provides context for AI agents working on this Nasse JS project.
|
|
149
|
+
|
|
150
|
+
## Stack
|
|
151
|
+
- Framework: Nasse JS (React 18+)
|
|
152
|
+
- Language: TypeScript
|
|
153
|
+
- Styling: ${answers.useTailwind ? 'Tailwind CSS' : 'Standard CSS'}
|
|
154
|
+
|
|
155
|
+
## Rules
|
|
156
|
+
1. Use \`app/index.tsx\` for the main page.
|
|
157
|
+
2. Server-side logic goes into \`export const server\` within page components.
|
|
158
|
+
3. Components belong in \`/components\`.
|
|
159
|
+
`);
|
|
160
|
+
|
|
161
|
+
// CLAUDE.md
|
|
162
|
+
fs.writeFileSync(path.join(targetDir, 'CLAUDE.md'), `# Claude Assistant Guidelines
|
|
163
|
+
|
|
164
|
+
When assisting with this Nasse JS project, please follow these rules:
|
|
165
|
+
- Prioritize React 18 functional components and hooks.
|
|
166
|
+
- Respect the file-based routing system in the \`app/\` directory.
|
|
167
|
+
- Use \`nasse.config.mjs\` for framework-specific settings.
|
|
168
|
+
`);
|
|
169
|
+
|
|
170
|
+
// jsconfig.json
|
|
171
|
+
fs.writeFileSync(path.join(targetDir, 'jsconfig.json'), JSON.stringify({
|
|
172
|
+
compilerOptions: {
|
|
173
|
+
baseUrl: ".",
|
|
174
|
+
paths: {
|
|
175
|
+
"@/*": ["./*"]
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
}, null, 2));
|
|
179
|
+
|
|
180
|
+
// eslint.config.mjs
|
|
181
|
+
fs.writeFileSync(path.join(targetDir, 'eslint.config.mjs'), `import js from "@eslint/js";
|
|
182
|
+
import reactPlugin from "eslint-plugin-react";
|
|
183
|
+
|
|
184
|
+
export default [
|
|
185
|
+
js.configs.recommended,
|
|
186
|
+
{
|
|
187
|
+
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"],
|
|
188
|
+
plugins: {
|
|
189
|
+
react: reactPlugin,
|
|
190
|
+
},
|
|
191
|
+
rules: {
|
|
192
|
+
"react/react-in-jsx-scope": "off",
|
|
193
|
+
},
|
|
194
|
+
languageOptions: {
|
|
195
|
+
parserOptions: {
|
|
196
|
+
ecmaFeatures: {
|
|
197
|
+
jsx: true,
|
|
198
|
+
},
|
|
199
|
+
},
|
|
200
|
+
},
|
|
201
|
+
},
|
|
202
|
+
];
|
|
203
|
+
`);
|
|
204
|
+
|
|
205
|
+
// nasse.config.mjs
|
|
206
|
+
fs.writeFileSync(path.join(targetDir, 'nasse.config.mjs'), `/** @type {import('nasse-js').NasseConfig} */
|
|
207
|
+
const nasseConfig = {
|
|
208
|
+
reactStrictMode: true,
|
|
209
|
+
// Add Nasse specific configurations here
|
|
210
|
+
};
|
|
211
|
+
|
|
212
|
+
export default nasseConfig;
|
|
213
|
+
`);
|
|
214
|
+
|
|
215
|
+
// nasse-env.d.ts
|
|
216
|
+
fs.writeFileSync(path.join(targetDir, 'nasse-env.d.ts'), `/// <reference types="nasse-js" />
|
|
217
|
+
/// <reference types="react" />
|
|
218
|
+
/// <reference types="react-dom" />
|
|
219
|
+
|
|
220
|
+
// NOTE: This file should not be edited
|
|
221
|
+
`);
|
|
222
|
+
|
|
223
|
+
// README.md
|
|
224
|
+
fs.writeFileSync(path.join(targetDir, 'README.md'), `# ${answers.projectName}
|
|
225
|
+
|
|
226
|
+
This is a modern web application built with [Nasse JS](https://github.com/nasse-nasse).
|
|
227
|
+
|
|
228
|
+
## Getting Started
|
|
229
|
+
|
|
230
|
+
First, run the development server:
|
|
231
|
+
|
|
232
|
+
\`\`\`bash
|
|
233
|
+
npm run dev
|
|
234
|
+
# or
|
|
235
|
+
yarn dev
|
|
236
|
+
# or
|
|
237
|
+
pnpm dev
|
|
238
|
+
\`\`\`
|
|
239
|
+
|
|
240
|
+
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
241
|
+
|
|
242
|
+
You can start editing the page by modifying \`app/index.tsx\`. The page auto-updates as you edit the file.
|
|
243
|
+
`);
|
|
244
|
+
|
|
245
|
+
// --- 4. OPTIONAL TAILWIND ---
|
|
246
|
+
if (answers.useTailwind) {
|
|
247
|
+
fs.writeFileSync(path.join(targetDir, 'tailwind.config.ts'), `import type { Config } from 'tailwindcss'
|
|
248
|
+
|
|
249
|
+
const config: Config = {
|
|
250
|
+
content: [
|
|
251
|
+
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
252
|
+
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
253
|
+
],
|
|
254
|
+
theme: {
|
|
255
|
+
extend: {
|
|
256
|
+
colors: {
|
|
257
|
+
nasse: '#8b5cf6',
|
|
258
|
+
},
|
|
259
|
+
},
|
|
260
|
+
},
|
|
261
|
+
plugins: [],
|
|
262
|
+
}
|
|
263
|
+
export default config
|
|
264
|
+
`);
|
|
265
|
+
|
|
266
|
+
fs.writeFileSync(path.join(targetDir, 'postcss.config.mjs'), `export default {
|
|
267
|
+
plugins: {
|
|
268
|
+
tailwindcss: {},
|
|
269
|
+
autoprefixer: {},
|
|
270
|
+
},
|
|
271
|
+
};
|
|
272
|
+
`);
|
|
273
|
+
|
|
274
|
+
fs.writeFileSync(path.join(targetDir, 'assets', 'global.css'), `@tailwind base;
|
|
275
|
+
@tailwind components;
|
|
276
|
+
@tailwind utilities;
|
|
277
|
+
|
|
278
|
+
:root {
|
|
279
|
+
color-scheme: dark;
|
|
280
|
+
}
|
|
281
|
+
`);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// --- 5. PACKAGE.JSON & TSCONFIG ---
|
|
285
|
+
const dependencies: Record<string, string> = {
|
|
286
|
+
"react": "^18.2.0",
|
|
287
|
+
"react-dom": "^18.2.0"
|
|
288
|
+
};
|
|
289
|
+
|
|
290
|
+
const devDependencies: Record<string, string> = {
|
|
291
|
+
"@types/react": "^18.2.0",
|
|
292
|
+
"@types/react-dom": "^18.2.0",
|
|
293
|
+
"typescript": "^5.2.2",
|
|
294
|
+
"nasse-js": "^1.0.1"
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
if (answers.useTailwind) {
|
|
298
|
+
devDependencies["tailwindcss"] = "^3.4.1";
|
|
299
|
+
devDependencies["postcss"] = "^8.4.35";
|
|
300
|
+
devDependencies["autoprefixer"] = "^10.4.17";
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
const pkgJson = {
|
|
304
|
+
name: answers.projectName,
|
|
305
|
+
version: "0.1.0",
|
|
306
|
+
private: true,
|
|
307
|
+
type: "module",
|
|
308
|
+
scripts: {
|
|
309
|
+
dev: "nasse dev",
|
|
310
|
+
build: "nasse build",
|
|
311
|
+
start: "nasse start"
|
|
312
|
+
},
|
|
313
|
+
dependencies,
|
|
314
|
+
devDependencies
|
|
315
|
+
};
|
|
316
|
+
|
|
317
|
+
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
318
|
+
|
|
319
|
+
const tsconfig = {
|
|
320
|
+
"compilerOptions": {
|
|
321
|
+
"target": "ES2022",
|
|
322
|
+
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
323
|
+
"allowJs": true,
|
|
324
|
+
"skipLibCheck": true,
|
|
325
|
+
"strict": true,
|
|
326
|
+
"noEmit": true,
|
|
327
|
+
"esModuleInterop": true,
|
|
328
|
+
"module": "ESNext",
|
|
329
|
+
"moduleResolution": "bundler",
|
|
330
|
+
"resolveJsonModule": true,
|
|
331
|
+
"isolatedModules": true,
|
|
332
|
+
"jsx": "preserve",
|
|
333
|
+
"incremental": true,
|
|
334
|
+
"plugins": [
|
|
335
|
+
{
|
|
336
|
+
"name": "next"
|
|
337
|
+
}
|
|
338
|
+
],
|
|
339
|
+
"paths": {
|
|
340
|
+
"@/*": ["./*"]
|
|
341
|
+
}
|
|
342
|
+
},
|
|
343
|
+
"include": ["nasse-env.d.ts", "**/*.ts", "**/*.tsx", ".nasse/types/**/*.ts"],
|
|
344
|
+
"exclude": ["node_modules"]
|
|
345
|
+
};
|
|
346
|
+
|
|
347
|
+
fs.writeFileSync(path.join(targetDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
348
|
+
|
|
349
|
+
// --- 6. INITIALIZE GIT ---
|
|
350
|
+
try {
|
|
351
|
+
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
352
|
+
execSync('git add .', { cwd: targetDir, stdio: 'ignore' });
|
|
353
|
+
// Don't commit yet to allow user to modify
|
|
354
|
+
} catch (e) {
|
|
355
|
+
// Git might not be installed
|
|
356
|
+
}
|
|
357
|
+
|
|
358
|
+
console.log(`\n${chalk.green('Success!')} Created ${answers.projectName}.`);
|
|
359
|
+
console.log(`Inside that directory, you can run:\n`);
|
|
360
|
+
console.log(` ${chalk.cyan('npm install')}`);
|
|
361
|
+
console.log(` ${chalk.cyan('npm run dev')}\n`);
|
|
362
|
+
console.log(`Happy coding! ⚡\n`);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (process.argv[1] === fileURLToPath(import.meta.url)) {
|
|
366
|
+
init().catch((e) => {
|
|
367
|
+
console.error(e);
|
|
368
|
+
});
|
|
369
|
+
}
|
package/src/cli/nasse.ts
ADDED
|
@@ -0,0 +1,116 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
import { Command } from 'commander';
|
|
4
|
+
import chalk from 'chalk';
|
|
5
|
+
import path from 'path';
|
|
6
|
+
import fs from 'fs';
|
|
7
|
+
import { createServer } from 'vite';
|
|
8
|
+
import express from 'express';
|
|
9
|
+
import compression from 'compression';
|
|
10
|
+
|
|
11
|
+
const program = new Command();
|
|
12
|
+
|
|
13
|
+
program
|
|
14
|
+
.name('nasse')
|
|
15
|
+
.description('Nasse JS - The React Framework for Humans')
|
|
16
|
+
.version('1.0.1');
|
|
17
|
+
|
|
18
|
+
program
|
|
19
|
+
.command('dev')
|
|
20
|
+
.description('Start the development server')
|
|
21
|
+
.action(async () => {
|
|
22
|
+
console.log(`\n ${chalk.bold.hex('#8b5cf6')('⚡')} ${chalk.bold('NASSE JS')} v1.0.1\n`);
|
|
23
|
+
|
|
24
|
+
const app = express();
|
|
25
|
+
const root = process.cwd();
|
|
26
|
+
|
|
27
|
+
// Optimization: Compression for faster transfers
|
|
28
|
+
app.use(compression());
|
|
29
|
+
|
|
30
|
+
// Create Vite server in middleware mode
|
|
31
|
+
const vite = await createServer({
|
|
32
|
+
root,
|
|
33
|
+
server: { middlewareMode: true },
|
|
34
|
+
appType: 'custom',
|
|
35
|
+
resolve: {
|
|
36
|
+
alias: {
|
|
37
|
+
'@': path.resolve(root, './src')
|
|
38
|
+
}
|
|
39
|
+
},
|
|
40
|
+
// Optimization: Cache and Build settings
|
|
41
|
+
optimizeDeps: {
|
|
42
|
+
include: ['react', 'react-dom']
|
|
43
|
+
}
|
|
44
|
+
});
|
|
45
|
+
|
|
46
|
+
app.use(vite.middlewares);
|
|
47
|
+
|
|
48
|
+
app.use('*', async (req, res, next) => {
|
|
49
|
+
const url = req.originalUrl;
|
|
50
|
+
|
|
51
|
+
try {
|
|
52
|
+
// Resolve route (File-System Routing)
|
|
53
|
+
let baseDir = fs.existsSync(path.resolve(root, 'src/app')) ? 'src/app' : 'app';
|
|
54
|
+
|
|
55
|
+
let routePath = path.resolve(root, `${baseDir}/index.tsx`);
|
|
56
|
+
if (url !== '/' && url !== '') {
|
|
57
|
+
const potentialPath = path.resolve(root, `${baseDir}${url}.tsx`);
|
|
58
|
+
if (fs.existsSync(potentialPath)) {
|
|
59
|
+
routePath = potentialPath;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Load modules
|
|
64
|
+
const layoutModule = await vite.ssrLoadModule(`/${baseDir}/_layout.tsx`);
|
|
65
|
+
const pageModule = await vite.ssrLoadModule(routePath.replace(root, ''));
|
|
66
|
+
|
|
67
|
+
const pageData = pageModule.server ? await pageModule.server() : {};
|
|
68
|
+
const meta = pageModule.meta || { title: 'Nasse App', description: 'Built with Nasse JS' };
|
|
69
|
+
|
|
70
|
+
const template = `<!DOCTYPE html>
|
|
71
|
+
<html lang="es">
|
|
72
|
+
<head>
|
|
73
|
+
<meta charset="UTF-8" />
|
|
74
|
+
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
75
|
+
<title>${meta.title}</title>
|
|
76
|
+
<meta name="description" content="${meta.description}" />
|
|
77
|
+
<link rel="icon" type="image/svg+xml" href="/favicon.svg" />
|
|
78
|
+
</head>
|
|
79
|
+
<body>
|
|
80
|
+
<div id="nasse-root"></div>
|
|
81
|
+
<script type="module">
|
|
82
|
+
window.__NASSE_DATA__ = ${JSON.stringify(pageData)};
|
|
83
|
+
console.log('Nasse JS: Ready for millions.');
|
|
84
|
+
</script>
|
|
85
|
+
</body>
|
|
86
|
+
</html>`;
|
|
87
|
+
|
|
88
|
+
const html = await vite.transformIndexHtml(url, template);
|
|
89
|
+
res.status(200).set({
|
|
90
|
+
'Content-Type': 'text/html',
|
|
91
|
+
'Cache-Control': 'no-store' // Dev mode should not cache
|
|
92
|
+
}).end(html);
|
|
93
|
+
|
|
94
|
+
} catch (e: any) {
|
|
95
|
+
vite.ssrFixStacktrace(e);
|
|
96
|
+
next(e);
|
|
97
|
+
}
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
const port = 3000;
|
|
101
|
+
app.listen(port, () => {
|
|
102
|
+
console.log(` ${chalk.green('➜')} Local: ${chalk.cyan(`http://localhost:${port}/`)}`);
|
|
103
|
+
console.log(` ${chalk.green('➜')} Network: ${chalk.dim('use --host to expose')}\n`);
|
|
104
|
+
});
|
|
105
|
+
});
|
|
106
|
+
|
|
107
|
+
program
|
|
108
|
+
.command('create [name]')
|
|
109
|
+
.description('Create a new Nasse JS project')
|
|
110
|
+
.action(async (name) => {
|
|
111
|
+
const { init } = await import('./create.js');
|
|
112
|
+
// If name is provided, we might need to handle it, but for now init() handles its own prompts
|
|
113
|
+
await init();
|
|
114
|
+
});
|
|
115
|
+
|
|
116
|
+
program.parse(process.argv);
|
package/dist/cli/create.js
DELETED
|
@@ -1,335 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import fs from 'fs';
|
|
3
|
-
import path from 'path';
|
|
4
|
-
import { fileURLToPath } from 'url';
|
|
5
|
-
import chalk from 'chalk';
|
|
6
|
-
import inquirer from 'inquirer';
|
|
7
|
-
import { execSync } from 'child_process';
|
|
8
|
-
const __filename = fileURLToPath(import.meta.url);
|
|
9
|
-
const __dirname = path.dirname(__filename);
|
|
10
|
-
async function init() {
|
|
11
|
-
console.log(`\n ${chalk.bold.hex('#8b5cf6')('⚡')} ${chalk.bold('OZONE JS')} - The React Framework for Humans\n`);
|
|
12
|
-
const answers = await inquirer.prompt([
|
|
13
|
-
{
|
|
14
|
-
type: 'input',
|
|
15
|
-
name: 'projectName',
|
|
16
|
-
message: 'What is your project named?',
|
|
17
|
-
default: 'my-ozone-app',
|
|
18
|
-
validate: (input) => {
|
|
19
|
-
if (/^([A-Za-z\-\_\d])+$/.test(input))
|
|
20
|
-
return true;
|
|
21
|
-
return 'Project name may only include letters, numbers, underscores and dashes.';
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
|
-
{
|
|
25
|
-
type: 'confirm',
|
|
26
|
-
name: 'useTailwind',
|
|
27
|
-
message: 'Would you like to use Tailwind CSS?',
|
|
28
|
-
default: true
|
|
29
|
-
}
|
|
30
|
-
]);
|
|
31
|
-
const targetDir = path.join(process.cwd(), answers.projectName);
|
|
32
|
-
if (fs.existsSync(targetDir)) {
|
|
33
|
-
console.error(chalk.red(`\nDirectory ${answers.projectName} already exists. Please choose another name.\n`));
|
|
34
|
-
process.exit(1);
|
|
35
|
-
}
|
|
36
|
-
console.log(`\nCreating a new Ozone app in ${chalk.green(targetDir)}.`);
|
|
37
|
-
fs.mkdirSync(targetDir, { recursive: true });
|
|
38
|
-
// Create standard folders
|
|
39
|
-
fs.mkdirSync(path.join(targetDir, 'app'), { recursive: true });
|
|
40
|
-
fs.mkdirSync(path.join(targetDir, 'public'), { recursive: true });
|
|
41
|
-
fs.mkdirSync(path.join(targetDir, 'components'), { recursive: true });
|
|
42
|
-
fs.mkdirSync(path.join(targetDir, 'assets'), { recursive: true });
|
|
43
|
-
// --- 1. GENERATE APP FILES ---
|
|
44
|
-
// app/index.tsx
|
|
45
|
-
fs.writeFileSync(path.join(targetDir, 'app', 'index.tsx'), `import React from 'react';
|
|
46
|
-
|
|
47
|
-
export const meta = {
|
|
48
|
-
title: "Welcome to Ozone JS",
|
|
49
|
-
description: "The React framework for humans."
|
|
50
|
-
};
|
|
51
|
-
|
|
52
|
-
export const server = async () => {
|
|
53
|
-
return {
|
|
54
|
-
message: "Server-side loaded data!"
|
|
55
|
-
};
|
|
56
|
-
};
|
|
57
|
-
|
|
58
|
-
export default function Home({ message }: { message: string }) {
|
|
59
|
-
return (
|
|
60
|
-
<div ${answers.useTailwind ? 'className="min-h-screen bg-gray-950 text-white flex flex-col items-center justify-center p-8"' : 'style={{ minHeight: "100vh", background: "#09090b", color: "white", padding: "2rem", display: "flex", flexDirection: "column", alignItems: "center", justifyContent: "center", fontFamily: "system-ui" }}'}>
|
|
61
|
-
<img src="/logo.svg" alt="Ozone Logo" ${answers.useTailwind ? 'className="w-24 h-24 mb-6"' : 'style={{ width: "96px", marginBottom: "1.5rem" }}'} />
|
|
62
|
-
<h1 ${answers.useTailwind ? 'className="text-4xl font-bold text-violet-500 mb-4"' : 'style={{ fontSize: "2.5rem", color: "#8b5cf6", margin: "0 0 1rem 0" }}'}>⚡ Ozone JS</h1>
|
|
63
|
-
<p ${answers.useTailwind ? 'className="text-gray-400 mb-8"' : 'style={{ color: "#a1a1aa", marginBottom: "2rem" }}'}>Welcome to your new app. Edit <code>app/index.tsx</code> to get started.</p>
|
|
64
|
-
<div ${answers.useTailwind ? 'className="p-4 bg-gray-900 rounded-lg border border-gray-800"' : 'style={{ padding: "1rem", background: "#18181b", borderRadius: "8px", border: "1px solid #27272a" }}'}>
|
|
65
|
-
<strong ${answers.useTailwind ? 'className="text-violet-400"' : 'style={{ color: "#a78bfa" }}'}>Data from server:</strong> {message}
|
|
66
|
-
</div>
|
|
67
|
-
</div>
|
|
68
|
-
);
|
|
69
|
-
}
|
|
70
|
-
`);
|
|
71
|
-
// app/_layout.tsx
|
|
72
|
-
const globalCssImport = answers.useTailwind ? `import '../assets/global.css';\n\n` : '';
|
|
73
|
-
fs.writeFileSync(path.join(targetDir, 'app', '_layout.tsx'), `import React from 'react';\n${globalCssImport}export default function RootLayout({ children }: { children: React.ReactNode }) {
|
|
74
|
-
return (
|
|
75
|
-
<html lang="en">
|
|
76
|
-
<head>
|
|
77
|
-
<meta charSet="utf-8" />
|
|
78
|
-
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
79
|
-
<link rel="icon" href="/favicon.ico" />
|
|
80
|
-
</head>
|
|
81
|
-
<body ${!answers.useTailwind ? 'style={{ margin: 0 }}' : ''}>
|
|
82
|
-
{children}
|
|
83
|
-
</body>
|
|
84
|
-
</html>
|
|
85
|
-
);
|
|
86
|
-
}
|
|
87
|
-
`);
|
|
88
|
-
// --- 2. GENERATE PUBLIC ASSETS ---
|
|
89
|
-
// public/favicon.ico (minimal base64 SVG or just an SVG masquerading as ICO for modern browsers)
|
|
90
|
-
fs.writeFileSync(path.join(targetDir, 'public', 'favicon.ico'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 100 100"><text y=".9em" font-size="90">⚡</text></svg>`);
|
|
91
|
-
// public/logo.svg
|
|
92
|
-
fs.writeFileSync(path.join(targetDir, 'public', 'logo.svg'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="#8b5cf6" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><path d="M13 2L3 14h9l-1 8 10-12h-9l1-8z"/></svg>`);
|
|
93
|
-
// public/globe.svg
|
|
94
|
-
fs.writeFileSync(path.join(targetDir, 'public', 'globe.svg'), `<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round"><circle cx="12" cy="12" r="10"/><path d="M12 2a15.3 15.3 0 0 1 4 10 15.3 15.3 0 0 1-4 10 15.3 15.3 0 0 1-4-10 15.3 15.3 0 0 1 4-10z"/><path d="M2 12h20"/></svg>`);
|
|
95
|
-
// --- 3. GENERATE ROOT CONFIG FILES ---
|
|
96
|
-
// .gitignore (100% git compatible)
|
|
97
|
-
fs.writeFileSync(path.join(targetDir, '.gitignore'), `# See https://help.github.com/articles/ignoring-files/
|
|
98
|
-
|
|
99
|
-
# dependencies
|
|
100
|
-
/node_modules
|
|
101
|
-
/.pnp
|
|
102
|
-
.pnp.js
|
|
103
|
-
|
|
104
|
-
# testing
|
|
105
|
-
/coverage
|
|
106
|
-
|
|
107
|
-
# ozone
|
|
108
|
-
.ozone
|
|
109
|
-
/dist
|
|
110
|
-
/build
|
|
111
|
-
|
|
112
|
-
# misc
|
|
113
|
-
.DS_Store
|
|
114
|
-
*.pem
|
|
115
|
-
|
|
116
|
-
# debug
|
|
117
|
-
npm-debug.log*
|
|
118
|
-
yarn-debug.log*
|
|
119
|
-
yarn-error.log*
|
|
120
|
-
|
|
121
|
-
# local env files
|
|
122
|
-
.env.local
|
|
123
|
-
.env.development.local
|
|
124
|
-
.env.test.local
|
|
125
|
-
.env.production.local
|
|
126
|
-
`);
|
|
127
|
-
// .env.local
|
|
128
|
-
fs.writeFileSync(path.join(targetDir, '.env.local'), `# Local environment variables
|
|
129
|
-
OZONE_PUBLIC_API_URL=http://localhost:3000/api
|
|
130
|
-
`);
|
|
131
|
-
// AGENTS.md
|
|
132
|
-
fs.writeFileSync(path.join(targetDir, 'AGENTS.md'), `# Project Agents Guidelines
|
|
133
|
-
|
|
134
|
-
This document provides context for AI agents working on this Ozone JS project.
|
|
135
|
-
|
|
136
|
-
## Stack
|
|
137
|
-
- Framework: Ozone JS (React 18+)
|
|
138
|
-
- Language: TypeScript
|
|
139
|
-
- Styling: ${answers.useTailwind ? 'Tailwind CSS' : 'Standard CSS'}
|
|
140
|
-
|
|
141
|
-
## Rules
|
|
142
|
-
1. Use \`app/index.tsx\` for the main page.
|
|
143
|
-
2. Server-side logic goes into \`export const server\` within page components.
|
|
144
|
-
3. Components belong in \`/components\`.
|
|
145
|
-
`);
|
|
146
|
-
// CLAUDE.md
|
|
147
|
-
fs.writeFileSync(path.join(targetDir, 'CLAUDE.md'), `# Claude Assistant Guidelines
|
|
148
|
-
|
|
149
|
-
When assisting with this Ozone JS project, please follow these rules:
|
|
150
|
-
- Prioritize React 18 functional components and hooks.
|
|
151
|
-
- Respect the file-based routing system in the \`app/\` directory.
|
|
152
|
-
- Use \`ozone.config.mjs\` for framework-specific settings.
|
|
153
|
-
`);
|
|
154
|
-
// jsconfig.json
|
|
155
|
-
fs.writeFileSync(path.join(targetDir, 'jsconfig.json'), JSON.stringify({
|
|
156
|
-
compilerOptions: {
|
|
157
|
-
baseUrl: ".",
|
|
158
|
-
paths: {
|
|
159
|
-
"@/*": ["./*"]
|
|
160
|
-
}
|
|
161
|
-
}
|
|
162
|
-
}, null, 2));
|
|
163
|
-
// eslint.config.mjs
|
|
164
|
-
fs.writeFileSync(path.join(targetDir, 'eslint.config.mjs'), `import js from "@eslint/js";
|
|
165
|
-
import reactPlugin from "eslint-plugin-react";
|
|
166
|
-
|
|
167
|
-
export default [
|
|
168
|
-
js.configs.recommended,
|
|
169
|
-
{
|
|
170
|
-
files: ["**/*.{js,jsx,mjs,cjs,ts,tsx}"],
|
|
171
|
-
plugins: {
|
|
172
|
-
react: reactPlugin,
|
|
173
|
-
},
|
|
174
|
-
rules: {
|
|
175
|
-
"react/react-in-jsx-scope": "off",
|
|
176
|
-
},
|
|
177
|
-
languageOptions: {
|
|
178
|
-
parserOptions: {
|
|
179
|
-
ecmaFeatures: {
|
|
180
|
-
jsx: true,
|
|
181
|
-
},
|
|
182
|
-
},
|
|
183
|
-
},
|
|
184
|
-
},
|
|
185
|
-
];
|
|
186
|
-
`);
|
|
187
|
-
// ozone.config.mjs
|
|
188
|
-
fs.writeFileSync(path.join(targetDir, 'ozone.config.mjs'), `/** @type {import('ozone-js').OzoneConfig} */
|
|
189
|
-
const ozoneConfig = {
|
|
190
|
-
reactStrictMode: true,
|
|
191
|
-
// Add Ozone specific configurations here
|
|
192
|
-
};
|
|
193
|
-
|
|
194
|
-
export default ozoneConfig;
|
|
195
|
-
`);
|
|
196
|
-
// ozone-env.d.ts
|
|
197
|
-
fs.writeFileSync(path.join(targetDir, 'ozone-env.d.ts'), `/// <reference types="ozone-js" />
|
|
198
|
-
/// <reference types="react" />
|
|
199
|
-
/// <reference types="react-dom" />
|
|
200
|
-
|
|
201
|
-
// NOTE: This file should not be edited
|
|
202
|
-
`);
|
|
203
|
-
// README.md
|
|
204
|
-
fs.writeFileSync(path.join(targetDir, 'README.md'), `# ${answers.projectName}
|
|
205
|
-
|
|
206
|
-
This is a modern web application built with [Ozone JS](https://github.com/nasse-ozone).
|
|
207
|
-
|
|
208
|
-
## Getting Started
|
|
209
|
-
|
|
210
|
-
First, run the development server:
|
|
211
|
-
|
|
212
|
-
\`\`\`bash
|
|
213
|
-
npm run dev
|
|
214
|
-
# or
|
|
215
|
-
yarn dev
|
|
216
|
-
# or
|
|
217
|
-
pnpm dev
|
|
218
|
-
\`\`\`
|
|
219
|
-
|
|
220
|
-
Open [http://localhost:3000](http://localhost:3000) with your browser to see the result.
|
|
221
|
-
|
|
222
|
-
You can start editing the page by modifying \`app/index.tsx\`. The page auto-updates as you edit the file.
|
|
223
|
-
`);
|
|
224
|
-
// --- 4. OPTIONAL TAILWIND ---
|
|
225
|
-
if (answers.useTailwind) {
|
|
226
|
-
fs.writeFileSync(path.join(targetDir, 'tailwind.config.ts'), `import type { Config } from 'tailwindcss'
|
|
227
|
-
|
|
228
|
-
const config: Config = {
|
|
229
|
-
content: [
|
|
230
|
-
'./app/**/*.{js,ts,jsx,tsx,mdx}',
|
|
231
|
-
'./components/**/*.{js,ts,jsx,tsx,mdx}',
|
|
232
|
-
],
|
|
233
|
-
theme: {
|
|
234
|
-
extend: {
|
|
235
|
-
colors: {
|
|
236
|
-
ozone: '#8b5cf6',
|
|
237
|
-
},
|
|
238
|
-
},
|
|
239
|
-
},
|
|
240
|
-
plugins: [],
|
|
241
|
-
}
|
|
242
|
-
export default config
|
|
243
|
-
`);
|
|
244
|
-
fs.writeFileSync(path.join(targetDir, 'postcss.config.mjs'), `export default {
|
|
245
|
-
plugins: {
|
|
246
|
-
tailwindcss: {},
|
|
247
|
-
autoprefixer: {},
|
|
248
|
-
},
|
|
249
|
-
};
|
|
250
|
-
`);
|
|
251
|
-
fs.writeFileSync(path.join(targetDir, 'assets', 'global.css'), `@tailwind base;
|
|
252
|
-
@tailwind components;
|
|
253
|
-
@tailwind utilities;
|
|
254
|
-
|
|
255
|
-
:root {
|
|
256
|
-
color-scheme: dark;
|
|
257
|
-
}
|
|
258
|
-
`);
|
|
259
|
-
}
|
|
260
|
-
// --- 5. PACKAGE.JSON & TSCONFIG ---
|
|
261
|
-
const dependencies = {
|
|
262
|
-
"react": "^18.2.0",
|
|
263
|
-
"react-dom": "^18.2.0"
|
|
264
|
-
};
|
|
265
|
-
const devDependencies = {
|
|
266
|
-
"@types/react": "^18.2.0",
|
|
267
|
-
"@types/react-dom": "^18.2.0",
|
|
268
|
-
"typescript": "^5.2.2",
|
|
269
|
-
"ozone-js": "file:.."
|
|
270
|
-
};
|
|
271
|
-
if (answers.useTailwind) {
|
|
272
|
-
devDependencies["tailwindcss"] = "^3.4.1";
|
|
273
|
-
devDependencies["postcss"] = "^8.4.35";
|
|
274
|
-
devDependencies["autoprefixer"] = "^10.4.17";
|
|
275
|
-
}
|
|
276
|
-
const pkgJson = {
|
|
277
|
-
name: answers.projectName,
|
|
278
|
-
version: "0.1.0",
|
|
279
|
-
private: true,
|
|
280
|
-
type: "module",
|
|
281
|
-
scripts: {
|
|
282
|
-
dev: "ozone dev",
|
|
283
|
-
build: "ozone build",
|
|
284
|
-
start: "ozone start"
|
|
285
|
-
},
|
|
286
|
-
dependencies,
|
|
287
|
-
devDependencies
|
|
288
|
-
};
|
|
289
|
-
fs.writeFileSync(path.join(targetDir, 'package.json'), JSON.stringify(pkgJson, null, 2));
|
|
290
|
-
const tsconfig = {
|
|
291
|
-
"compilerOptions": {
|
|
292
|
-
"target": "ES2022",
|
|
293
|
-
"lib": ["DOM", "DOM.Iterable", "ES2022"],
|
|
294
|
-
"allowJs": true,
|
|
295
|
-
"skipLibCheck": true,
|
|
296
|
-
"strict": true,
|
|
297
|
-
"noEmit": true,
|
|
298
|
-
"esModuleInterop": true,
|
|
299
|
-
"module": "ESNext",
|
|
300
|
-
"moduleResolution": "bundler",
|
|
301
|
-
"resolveJsonModule": true,
|
|
302
|
-
"isolatedModules": true,
|
|
303
|
-
"jsx": "preserve",
|
|
304
|
-
"incremental": true,
|
|
305
|
-
"plugins": [
|
|
306
|
-
{
|
|
307
|
-
"name": "next"
|
|
308
|
-
}
|
|
309
|
-
],
|
|
310
|
-
"paths": {
|
|
311
|
-
"@/*": ["./*"]
|
|
312
|
-
}
|
|
313
|
-
},
|
|
314
|
-
"include": ["ozone-env.d.ts", "**/*.ts", "**/*.tsx", ".ozone/types/**/*.ts"],
|
|
315
|
-
"exclude": ["node_modules"]
|
|
316
|
-
};
|
|
317
|
-
fs.writeFileSync(path.join(targetDir, 'tsconfig.json'), JSON.stringify(tsconfig, null, 2));
|
|
318
|
-
// --- 6. INITIALIZE GIT ---
|
|
319
|
-
try {
|
|
320
|
-
execSync('git init', { cwd: targetDir, stdio: 'ignore' });
|
|
321
|
-
execSync('git add .', { cwd: targetDir, stdio: 'ignore' });
|
|
322
|
-
// Don't commit yet to allow user to modify
|
|
323
|
-
}
|
|
324
|
-
catch (e) {
|
|
325
|
-
// Git might not be installed
|
|
326
|
-
}
|
|
327
|
-
console.log(`\n${chalk.green('Success!')} Created ${answers.projectName}.`);
|
|
328
|
-
console.log(`Inside that directory, you can run:\n`);
|
|
329
|
-
console.log(` ${chalk.cyan('npm install')}`);
|
|
330
|
-
console.log(` ${chalk.cyan('npm run dev')}\n`);
|
|
331
|
-
console.log(`Happy coding! ⚡\n`);
|
|
332
|
-
}
|
|
333
|
-
init().catch((e) => {
|
|
334
|
-
console.error(e);
|
|
335
|
-
});
|
package/dist/cli/ozone.js
DELETED
|
@@ -1,79 +0,0 @@
|
|
|
1
|
-
#!/usr/bin/env node
|
|
2
|
-
import { Command } from 'commander';
|
|
3
|
-
import chalk from 'chalk';
|
|
4
|
-
import path from 'path';
|
|
5
|
-
import { createServer } from 'vite';
|
|
6
|
-
import express from 'express';
|
|
7
|
-
const program = new Command();
|
|
8
|
-
program
|
|
9
|
-
.name('ozone')
|
|
10
|
-
.description('Ozone JS - The React Framework for Humans')
|
|
11
|
-
.version('1.0.0');
|
|
12
|
-
program
|
|
13
|
-
.command('dev')
|
|
14
|
-
.description('Start the development server')
|
|
15
|
-
.action(async () => {
|
|
16
|
-
console.log(`\n ${chalk.bold.hex('#8b5cf6')('⚡')} ${chalk.bold('OZONE JS')} v1.0.0\n`);
|
|
17
|
-
const app = express();
|
|
18
|
-
// Create Vite server in middleware mode
|
|
19
|
-
const vite = await createServer({
|
|
20
|
-
server: { middlewareMode: true },
|
|
21
|
-
appType: 'custom',
|
|
22
|
-
resolve: {
|
|
23
|
-
alias: {
|
|
24
|
-
'@': path.resolve(process.cwd(), './app')
|
|
25
|
-
}
|
|
26
|
-
}
|
|
27
|
-
});
|
|
28
|
-
// Use vite's connect instance as middleware
|
|
29
|
-
app.use(vite.middlewares);
|
|
30
|
-
app.use('*', async (req, res, next) => {
|
|
31
|
-
const url = req.originalUrl;
|
|
32
|
-
try {
|
|
33
|
-
// In a complete implementation, this would map the URL to the file system route,
|
|
34
|
-
// evaluate the 'server' export, inject it into the HTML, and SSR the component.
|
|
35
|
-
// For this minimal version, we will serve a basic HTML shell that loads client entry.
|
|
36
|
-
// This is a placeholder for the actual SSR logic.
|
|
37
|
-
const template = `<!DOCTYPE html>
|
|
38
|
-
<html lang="en">
|
|
39
|
-
<head>
|
|
40
|
-
<meta charset="UTF-8" />
|
|
41
|
-
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
42
|
-
<title>Ozone App</title>
|
|
43
|
-
<!-- In a real app we'd inject vite client and the entrypoint -->
|
|
44
|
-
</head>
|
|
45
|
-
<body>
|
|
46
|
-
<div id="root"></div>
|
|
47
|
-
<script type="module">
|
|
48
|
-
// Import the file system router and mount the app
|
|
49
|
-
console.log('Ozone JS client mounted!');
|
|
50
|
-
</script>
|
|
51
|
-
</body>
|
|
52
|
-
</html>`;
|
|
53
|
-
const html = await vite.transformIndexHtml(url, template);
|
|
54
|
-
res.status(200).set({ 'Content-Type': 'text/html' }).end(html);
|
|
55
|
-
}
|
|
56
|
-
catch (e) {
|
|
57
|
-
vite.ssrFixStacktrace(e);
|
|
58
|
-
next(e);
|
|
59
|
-
}
|
|
60
|
-
});
|
|
61
|
-
const port = 3000;
|
|
62
|
-
app.listen(port, () => {
|
|
63
|
-
console.log(` ${chalk.green('➜')} Local: ${chalk.cyan(`http://localhost:${port}/`)}`);
|
|
64
|
-
console.log(` ${chalk.green('➜')} Network: ${chalk.dim('use --host to expose')}\n`);
|
|
65
|
-
});
|
|
66
|
-
});
|
|
67
|
-
program
|
|
68
|
-
.command('build')
|
|
69
|
-
.description('Build the app for production')
|
|
70
|
-
.action(() => {
|
|
71
|
-
console.log(chalk.yellow('\nBuilding for production is not implemented in this minimal prototype.\n'));
|
|
72
|
-
});
|
|
73
|
-
program
|
|
74
|
-
.command('start')
|
|
75
|
-
.description('Start the production server')
|
|
76
|
-
.action(() => {
|
|
77
|
-
console.log(chalk.yellow('\nProduction server is not implemented in this minimal prototype.\n'));
|
|
78
|
-
});
|
|
79
|
-
program.parse(process.argv);
|