create-fuzionx 0.1.5

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/index.js ADDED
@@ -0,0 +1,137 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * create-fuzionx — FuzionX 앱 스캐폴딩 CLI
4
+ *
5
+ * Usage:
6
+ * npx create-fuzionx my-app
7
+ * npx create-fuzionx my-app /path/to/dir
8
+ */
9
+ import { promises as fs } from 'node:fs';
10
+ import path from 'node:path';
11
+ import { fileURLToPath } from 'node:url';
12
+
13
+ const __dirname = path.dirname(fileURLToPath(import.meta.url));
14
+ const TPL_DIR = path.join(__dirname, 'templates');
15
+
16
+ // ── 템플릿 엔진 ({{var}} → value) ──
17
+
18
+ function render(template, vars) {
19
+ return template.replace(/\{\{(\w+)\}\}/g, (_, key) => vars[key] ?? '');
20
+ }
21
+
22
+ async function loadTemplate(relPath, vars) {
23
+ const raw = await fs.readFile(path.join(TPL_DIR, relPath), 'utf-8');
24
+ return render(raw, vars);
25
+ }
26
+
27
+ // ── 스캐폴딩 파일 목록 ──
28
+
29
+ const APP_FILES = [
30
+ { tpl: 'package.json.tpl', dest: 'package.json' },
31
+ { tpl: 'fuzionx.yaml.tpl', dest: 'fuzionx.yaml' },
32
+ { tpl: 'app.js.tpl', dest: 'app.js' },
33
+ { tpl: 'routes/web.js.tpl', dest: 'routes/web.js' },
34
+ { tpl: 'routes/api.js.tpl', dest: 'routes/api.js' },
35
+ { tpl: '.env.example.tpl', dest: '.env.example' },
36
+ { tpl: '.env.example.tpl', dest: '.env' },
37
+ { tpl: '.gitignore.tpl', dest: '.gitignore' },
38
+ ];
39
+
40
+ const APP_DIRS = [
41
+ 'controllers',
42
+ 'models',
43
+ 'services',
44
+ 'middleware',
45
+ 'ws',
46
+ 'jobs',
47
+ 'events',
48
+ 'workers',
49
+ 'migrations',
50
+ 'seeds',
51
+ 'storage/logs',
52
+ 'storage/uploads',
53
+ 'public/css',
54
+ 'public/js',
55
+ 'locales',
56
+ 'tests',
57
+ ];
58
+
59
+ // ── createApp ──
60
+
61
+ async function createApp(name, targetDir) {
62
+ const dir = targetDir || path.resolve(name);
63
+ const vars = {
64
+ name,
65
+ dbName: name.replace(/-/g, '_'),
66
+ };
67
+
68
+ // 디렉토리 생성
69
+ for (const d of APP_DIRS) {
70
+ await fs.mkdir(path.join(dir, d), { recursive: true });
71
+ await fs.writeFile(path.join(dir, d, '.gitkeep'), '');
72
+ }
73
+
74
+ // 템플릿 파일 생성
75
+ for (const { tpl, dest } of APP_FILES) {
76
+ const content = await loadTemplate(tpl, vars);
77
+ const fullPath = path.join(dir, dest);
78
+ await fs.mkdir(path.dirname(fullPath), { recursive: true });
79
+ await fs.writeFile(fullPath, content);
80
+ }
81
+
82
+ // HomeController 복사
83
+ const hcSrc = path.join(TPL_DIR, 'controllers/HomeController.js');
84
+ const hcDst = path.join(dir, 'controllers/HomeController.js');
85
+ await fs.copyFile(hcSrc, hcDst);
86
+
87
+ // Views — views/{theme}/ 구조
88
+ const viewsSrc = path.join(TPL_DIR, 'views/default');
89
+ const viewsDst = path.join(dir, 'views/default');
90
+
91
+ const layoutDir = path.join(viewsDst, 'layouts');
92
+ await fs.mkdir(layoutDir, { recursive: true });
93
+ await fs.copyFile(path.join(viewsSrc, 'layouts/main.html'), path.join(layoutDir, 'main.html'));
94
+
95
+ const pagesDir = path.join(viewsDst, 'pages');
96
+ await fs.mkdir(pagesDir, { recursive: true });
97
+ await fs.copyFile(path.join(viewsSrc, 'pages/home.html'), path.join(pagesDir, 'home.html'));
98
+
99
+ const errDir = path.join(viewsDst, 'errors');
100
+ await fs.mkdir(errDir, { recursive: true });
101
+ await fs.copyFile(path.join(viewsSrc, 'errors/404.html'), path.join(errDir, '404.html'));
102
+ await fs.copyFile(path.join(viewsSrc, 'errors/500.html'), path.join(errDir, '500.html'));
103
+
104
+ return dir;
105
+ }
106
+
107
+ // ── CLI 엔트리 ──
108
+
109
+ const name = process.argv[2];
110
+ const targetDir = process.argv[3];
111
+
112
+ if (!name) {
113
+ console.error(`
114
+ Usage: npx create-fuzionx <app-name> [target-dir]
115
+
116
+ Examples:
117
+ npx create-fuzionx my-app
118
+ npx create-fuzionx my-app /path/to/dir
119
+ `);
120
+ process.exit(1);
121
+ }
122
+
123
+ try {
124
+ const dir = await createApp(name, targetDir);
125
+ console.log(`
126
+ ✅ Created ${name} at ${dir}
127
+
128
+ Next steps:
129
+
130
+ cd ${name}
131
+ npm install
132
+ npx fx dev
133
+ `);
134
+ } catch (err) {
135
+ console.error(`❌ Failed to create app: ${err.message}`);
136
+ process.exit(1);
137
+ }
package/package.json ADDED
@@ -0,0 +1,22 @@
1
+ {
2
+ "name": "create-fuzionx",
3
+ "version": "0.1.5",
4
+ "description": "Create a new FuzionX application — npx create-fuzionx my-app",
5
+ "type": "module",
6
+ "bin": {
7
+ "create-fuzionx": "./index.js"
8
+ },
9
+ "keywords": ["fuzionx", "create", "scaffold", "cli", "framework"],
10
+ "license": "MIT",
11
+ "repository": {
12
+ "type": "git",
13
+ "url": "https://github.com/saytohenry/fuzionx"
14
+ },
15
+ "engines": {
16
+ "node": ">=18.0.0"
17
+ },
18
+ "files": [
19
+ "index.js",
20
+ "templates/"
21
+ ]
22
+ }
@@ -0,0 +1,14 @@
1
+ # .env — 이 파일을 .env로 복사 후 값을 채우세요
2
+
3
+ # Database
4
+ DB_HOST=127.0.0.1
5
+ DB_PORT=3306
6
+ DB_USER=root
7
+ DB_PASSWORD=
8
+ DB_NAME={{dbName}}
9
+
10
+ # Auth
11
+ JWT_SECRET=
12
+
13
+ # Redis (세션/큐 공용)
14
+ REDIS_URL=
@@ -0,0 +1,4 @@
1
+ .env
2
+ node_modules/
3
+ storage/logs/
4
+ storage/uploads/
@@ -0,0 +1,14 @@
1
+ import { Application } from '@fuzionx/framework';
2
+ import webRoutes from './routes/web.js';
3
+ import apiRoutes from './routes/api.js';
4
+
5
+ const app = new Application({ configPath: './fuzionx.yaml' });
6
+
7
+ app.routes(webRoutes);
8
+ app.routes(apiRoutes);
9
+
10
+ await app.boot();
11
+
12
+ app.listen(49080, () => {
13
+ console.log('🚀 FuzionX running on http://localhost:49080');
14
+ });
@@ -0,0 +1,13 @@
1
+ import { Controller } from '@fuzionx/framework';
2
+
3
+ /**
4
+ * Home 컨트롤러.
5
+ * routes/web.js에서 라우트를 등록하세요.
6
+ */
7
+ export default class HomeController extends Controller {
8
+
9
+ /** 홈 페이지 */
10
+ async index(ctx) {
11
+ ctx.render('pages/home');
12
+ }
13
+ }
@@ -0,0 +1,30 @@
1
+ # FuzionX Configuration
2
+ bridge:
3
+ port: 49080
4
+ workers: 4
5
+ worker_timeout: 30
6
+
7
+ rate_limit:
8
+ enabled: true
9
+ per_ip: 1000
10
+
11
+ database:
12
+ main:
13
+ driver: sqlite
14
+ path: ./storage/database.sqlite
15
+
16
+ app:
17
+ name: '{{name}}'
18
+ environment: development
19
+ auth:
20
+ secret: 'change-me-in-production'
21
+ accessTtl: '15m'
22
+ i18n:
23
+ default_locale: 'ko'
24
+ fallback: 'en'
25
+ docs:
26
+ enabled: true
27
+ path: '/docs'
28
+
29
+ themes:
30
+ default: 'default'
@@ -0,0 +1,15 @@
1
+ {
2
+ "name": "{{name}}",
3
+ "version": "1.0.0",
4
+ "type": "module",
5
+ "scripts": {
6
+ "dev": "fx dev",
7
+ "test": "vitest run"
8
+ },
9
+ "dependencies": {
10
+ "@fuzionx/framework": "^0.1.0"
11
+ },
12
+ "devDependencies": {
13
+ "vitest": "^3.0.0"
14
+ }
15
+ }
@@ -0,0 +1,7 @@
1
+ export default (r) => {
2
+ r.group('/api', (r) => {
3
+ r.get('/health', (ctx) => {
4
+ ctx.json({ status: 'ok', timestamp: Date.now() });
5
+ });
6
+ });
7
+ };
@@ -0,0 +1,5 @@
1
+ export default (r) => {
2
+ r.get('/', (ctx) => {
3
+ ctx.render('home');
4
+ });
5
+ };
@@ -0,0 +1,15 @@
1
+ {% extends "layouts/main.html" %}
2
+
3
+ {% block title %}404 — 찾을 수 없습니다{% endblock %}
4
+
5
+ {% block content %}
6
+ <div style="text-align:center;padding:80px 20px;">
7
+ <h1 style="font-size:72px;color:#e74c3c;">{{ error.code }}</h1>
8
+ <p style="font-size:20px;margin:16px 0;">{{ error.message }}</p>
9
+ <p style="color:#888;">요청: {{ request.url }}</p>
10
+ {% if config.debug %}
11
+ <pre style="text-align:left;max-width:600px;margin:24px auto;background:#f5f5f5;padding:16px;border-radius:8px;overflow:auto;">{{ error.stack }}</pre>
12
+ {% endif %}
13
+ <a href="/" style="display:inline-block;margin-top:24px;padding:12px 24px;background:#e74c3c;color:#fff;text-decoration:none;border-radius:6px;">홈으로 돌아가기</a>
14
+ </div>
15
+ {% endblock %}
@@ -0,0 +1,14 @@
1
+ {% extends "layouts/main.html" %}
2
+
3
+ {% block title %}500 — 서버 오류{% endblock %}
4
+
5
+ {% block content %}
6
+ <div style="text-align:center;padding:80px 20px;">
7
+ <h1 style="font-size:72px;color:#e74c3c;">500</h1>
8
+ <p style="font-size:20px;margin:16px 0;">{{ error.message | default(value='Internal Server Error') }}</p>
9
+ {% if config.debug and error.stack %}
10
+ <pre style="text-align:left;max-width:600px;margin:24px auto;background:#f5f5f5;padding:16px;border-radius:8px;overflow:auto;">{{ error.stack }}</pre>
11
+ {% endif %}
12
+ <a href="/" style="display:inline-block;margin-top:24px;padding:12px 24px;background:#e74c3c;color:#fff;text-decoration:none;border-radius:6px;">홈으로 돌아가기</a>
13
+ </div>
14
+ {% endblock %}
@@ -0,0 +1,22 @@
1
+ <!DOCTYPE html>
2
+ <html lang="{{ locale | default(value='ko') }}">
3
+ <head>
4
+ <meta charset="utf-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1">
6
+ <title>{% block title %}{{ config.app.name | default(value='FuzionX') }}{% endblock %}</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ body { font-family: system-ui, -apple-system, sans-serif; line-height: 1.6; color: #333; }
10
+ </style>
11
+ {% block head %}{% endblock %}
12
+ </head>
13
+ <body>
14
+ {% include "partials/header.html" ignore missing %}
15
+
16
+ <main>
17
+ {% block content %}{% endblock %}
18
+ </main>
19
+
20
+ {% include "partials/footer.html" ignore missing %}
21
+ </body>
22
+ </html>
@@ -0,0 +1,188 @@
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>FuzionX</title>
7
+ <style>
8
+ * { margin: 0; padding: 0; box-sizing: border-box; }
9
+ @import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;600;800&display=swap');
10
+
11
+ body {
12
+ font-family: 'Inter', system-ui, -apple-system, sans-serif;
13
+ min-height: 100vh;
14
+ display: flex;
15
+ align-items: center;
16
+ justify-content: center;
17
+ background: linear-gradient(135deg, #0f0c29 0%, #1a1a3e 40%, #24243e 100%);
18
+ color: #e0e0e0;
19
+ overflow: hidden;
20
+ }
21
+
22
+ .container {
23
+ text-align: center;
24
+ z-index: 1;
25
+ animation: fadeInUp 0.8s ease-out;
26
+ }
27
+
28
+ .logo {
29
+ font-size: 4rem;
30
+ font-weight: 800;
31
+ background: linear-gradient(135deg, #667eea 0%, #764ba2 50%, #f093fb 100%);
32
+ -webkit-background-clip: text;
33
+ background-clip: text;
34
+ -webkit-text-fill-color: transparent;
35
+ letter-spacing: -2px;
36
+ margin-bottom: 0.5rem;
37
+ }
38
+
39
+ .subtitle {
40
+ font-size: 1.1rem;
41
+ font-weight: 300;
42
+ color: rgba(255, 255, 255, 0.5);
43
+ margin-bottom: 2.5rem;
44
+ letter-spacing: 2px;
45
+ }
46
+
47
+ .card {
48
+ background: rgba(255, 255, 255, 0.05);
49
+ backdrop-filter: blur(20px);
50
+ border: 1px solid rgba(255, 255, 255, 0.1);
51
+ border-radius: 16px;
52
+ padding: 2rem 3rem;
53
+ max-width: 480px;
54
+ margin: 0 auto 2rem;
55
+ }
56
+
57
+ .version {
58
+ display: inline-block;
59
+ background: linear-gradient(135deg, #667eea, #764ba2);
60
+ color: white;
61
+ padding: 4px 14px;
62
+ border-radius: 20px;
63
+ font-size: 0.75rem;
64
+ font-weight: 600;
65
+ letter-spacing: 1px;
66
+ margin-bottom: 1.5rem;
67
+ }
68
+
69
+ .features {
70
+ display: grid;
71
+ grid-template-columns: 1fr 1fr;
72
+ gap: 1rem;
73
+ text-align: left;
74
+ margin-top: 1.5rem;
75
+ }
76
+
77
+ .feature {
78
+ display: flex;
79
+ align-items: center;
80
+ gap: 8px;
81
+ font-size: 0.85rem;
82
+ color: rgba(255, 255, 255, 0.7);
83
+ }
84
+
85
+ .feature span {
86
+ font-size: 1.1rem;
87
+ }
88
+
89
+ .links {
90
+ display: flex;
91
+ gap: 1rem;
92
+ justify-content: center;
93
+ margin-top: 1rem;
94
+ }
95
+
96
+ .links a {
97
+ color: rgba(255, 255, 255, 0.6);
98
+ text-decoration: none;
99
+ font-size: 0.85rem;
100
+ padding: 8px 20px;
101
+ border: 1px solid rgba(255, 255, 255, 0.15);
102
+ border-radius: 8px;
103
+ transition: all 0.3s ease;
104
+ }
105
+
106
+ .links a:hover {
107
+ color: #fff;
108
+ border-color: #667eea;
109
+ background: rgba(102, 126, 234, 0.1);
110
+ transform: translateY(-2px);
111
+ }
112
+
113
+ .hint {
114
+ margin-top: 2rem;
115
+ font-size: 0.75rem;
116
+ color: rgba(255, 255, 255, 0.3);
117
+ }
118
+
119
+ .hint code {
120
+ background: rgba(255, 255, 255, 0.08);
121
+ padding: 2px 8px;
122
+ border-radius: 4px;
123
+ font-family: 'JetBrains Mono', monospace;
124
+ }
125
+
126
+ /* Background orbs */
127
+ .orb {
128
+ position: fixed;
129
+ border-radius: 50%;
130
+ filter: blur(80px);
131
+ opacity: 0.3;
132
+ animation: float 8s ease-in-out infinite;
133
+ }
134
+ .orb-1 { width: 400px; height: 400px; background: #667eea; top: -100px; right: -100px; }
135
+ .orb-2 { width: 300px; height: 300px; background: #764ba2; bottom: -80px; left: -80px; animation-delay: -4s; }
136
+ .orb-3 { width: 200px; height: 200px; background: #f093fb; top: 50%; left: 60%; animation-delay: -2s; }
137
+
138
+ @keyframes fadeInUp {
139
+ from { opacity: 0; transform: translateY(30px); }
140
+ to { opacity: 1; transform: translateY(0); }
141
+ }
142
+
143
+ @keyframes float {
144
+ 0%, 100% { transform: translate(0, 0); }
145
+ 50% { transform: translate(30px, -30px); }
146
+ }
147
+
148
+ @media (max-width: 600px) {
149
+ .logo { font-size: 2.5rem; }
150
+ .card { padding: 1.5rem; margin: 0 1rem; }
151
+ .features { grid-template-columns: 1fr; }
152
+ }
153
+ </style>
154
+ </head>
155
+ <body>
156
+ <div class="orb orb-1"></div>
157
+ <div class="orb orb-2"></div>
158
+ <div class="orb orb-3"></div>
159
+
160
+ <div class="container">
161
+ <h1 class="logo">FuzionX</h1>
162
+ <p class="subtitle">HIGH-PERFORMANCE NODE.JS FRAMEWORK</p>
163
+
164
+ <div class="card">
165
+ <div class="version">v0.1.0 · POWERED BY RUST</div>
166
+
167
+ <div class="features">
168
+ <div class="feature"><span>⚡</span> 500K+ RPS</div>
169
+ <div class="feature"><span>🦀</span> Rust N-API Bridge</div>
170
+ <div class="feature"><span>🎯</span> MVC Architecture</div>
171
+ <div class="feature"><span>🔌</span> WebSocket</div>
172
+ <div class="feature"><span>🗄️</span> Multi-DB ORM</div>
173
+ <div class="feature"><span>🔐</span> Auth & Session</div>
174
+ <div class="feature"><span>📡</span> Event System</div>
175
+ <div class="feature"><span>⏰</span> Job Scheduler</div>
176
+ </div>
177
+ </div>
178
+
179
+ <div class="links">
180
+ <a href="https://github.com/saytohenry/fuzionx">GitHub</a>
181
+ <a href="/docs">API Docs</a>
182
+ <a href="/api/health">Health Check</a>
183
+ </div>
184
+
185
+ <p class="hint">Edit <code>routes/web.js</code> to get started</p>
186
+ </div>
187
+ </body>
188
+ </html>