luxaura 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 +365 -208
- package/bin/luxaura.js +813 -394
- package/examples/TodoApp.lux +89 -83
- package/package.json +4 -12
- package/src/compiler/index.js +258 -251
- package/src/index.js +12 -23
- package/src/parser/index.js +162 -185
- package/src/runtime/luxaura.runtime.js +277 -266
- package/src/vault/server.js +88 -131
- package/ui-kit/luxaura.min.css +459 -605
- package/ui-kit/luxaura.min.js +219 -169
- package/templates/luxaura.config +0 -43
package/bin/luxaura.js
CHANGED
|
@@ -2,8 +2,34 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
* Luxaura CLI
|
|
6
|
-
*
|
|
5
|
+
* Luxaura CLI v2.0
|
|
6
|
+
*
|
|
7
|
+
* Commands (ALL renamed — nothing similar to React/Vue/Angular/Next CLI):
|
|
8
|
+
*
|
|
9
|
+
* luxaura release <name> → Create a new project (was: new)
|
|
10
|
+
* luxaura ignite → Start dev server + live reload (was: run)
|
|
11
|
+
* luxaura forge → Build for production (was: build)
|
|
12
|
+
* luxaura bring <lib> → Add external library (was: install)
|
|
13
|
+
* luxaura component <name> → Scaffold a UI component (was: generate component)
|
|
14
|
+
* luxaura service <name> → Scaffold a vault service (was: generate api)
|
|
15
|
+
* luxaura module <name> → Scaffold a shared module (was: generate page)
|
|
16
|
+
* luxaura help_me → Interactive CLI guide
|
|
17
|
+
* luxaura polish → Auto-format .lux files (was: format)
|
|
18
|
+
* luxaura shield → Security scan (was: secure)
|
|
19
|
+
*
|
|
20
|
+
* Project folder hierarchy (ALL renamed):
|
|
21
|
+
*
|
|
22
|
+
* /scenes → route entry points (was: /pages — Next.js-like)
|
|
23
|
+
* /craft → root components live here (was: /modules — Vue-like)
|
|
24
|
+
* /craft/<ComponentName>/
|
|
25
|
+
* <ComponentName>.lux
|
|
26
|
+
* <ComponentName>.paint.css ← per-component styles
|
|
27
|
+
* index.js ← optional JS bridge
|
|
28
|
+
* /services → vault/server logic (was: /server)
|
|
29
|
+
* /media → static assets (was: /assets)
|
|
30
|
+
* /depot → build output (was: /dist)
|
|
31
|
+
* /depot/public → client assets (was: dist/client)
|
|
32
|
+
* /depot/vault → server modules (was: dist/server)
|
|
7
33
|
*/
|
|
8
34
|
|
|
9
35
|
const { program } = require('commander');
|
|
@@ -17,451 +43,866 @@ const { LuxParser } = require('../src/parser');
|
|
|
17
43
|
const { LuxCompiler } = require('../src/compiler');
|
|
18
44
|
const { VaultServer } = require('../src/vault/server');
|
|
19
45
|
|
|
20
|
-
const VERSION = '
|
|
46
|
+
const VERSION = '2.0.0';
|
|
21
47
|
|
|
22
48
|
// ─── Banner ──────────────────────────────────────────────────────────────────
|
|
23
49
|
|
|
24
50
|
function banner() {
|
|
25
|
-
console.log(chalk.
|
|
51
|
+
console.log(chalk.hex('#6c63ff')(`
|
|
26
52
|
██╗ ██╗ ██╗██╗ ██╗ █████╗ ██╗ ██╗██████╗ █████╗
|
|
27
53
|
██║ ██║ ██║╚██╗██╔╝██╔══██╗██║ ██║██╔══██╗██╔══██╗
|
|
28
54
|
██║ ██║ ██║ ╚███╔╝ ███████║██║ ██║██████╔╝███████║
|
|
29
55
|
██║ ██║ ██║ ██╔██╗ ██╔══██║██║ ██║██╔══██╗██╔══██║
|
|
30
56
|
███████╗╚██████╔╝██╔╝ ██╗██║ ██║╚██████╔╝██║ ██║██║ ██║
|
|
31
|
-
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝
|
|
32
|
-
`));
|
|
33
|
-
console.log(chalk.gray(`
|
|
57
|
+
╚══════╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝ ╚═════╝ ╚═╝ ╚═╝╚═╝ ╚═╝`));
|
|
58
|
+
console.log(chalk.hex('#a8a3ff')(` The Intent-Driven Full-Stack Web Framework`));
|
|
59
|
+
console.log(chalk.gray(` v${VERSION} · Not React. Not Vue. Not Angular. Just Luxaura.\n`));
|
|
34
60
|
}
|
|
35
61
|
|
|
36
|
-
// ───
|
|
62
|
+
// ─── Logging helpers ─────────────────────────────────────────────────────────
|
|
37
63
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
64
|
+
const log = (m) => console.log(chalk.hex('#6c63ff')(' ◆'), m);
|
|
65
|
+
const success = (m) => console.log(chalk.greenBright(' ✦'), m);
|
|
66
|
+
const warn = (m) => console.log(chalk.yellow(' ⚠'), m);
|
|
67
|
+
const error = (m) => console.error(chalk.redBright(' ✖'), m);
|
|
68
|
+
const info = (m) => console.log(chalk.gray(' ·'), m);
|
|
69
|
+
const dim = (m) => console.log(chalk.gray(m));
|
|
43
70
|
|
|
44
|
-
|
|
45
|
-
const source = fs.readFileSync(filePath, 'utf8');
|
|
46
|
-
const filename = path.basename(filePath);
|
|
47
|
-
const parser = new LuxParser(source, filename);
|
|
48
|
-
const ast = parser.parse();
|
|
49
|
-
const compiler = new LuxCompiler(ast, options);
|
|
50
|
-
return compiler.compile();
|
|
51
|
-
}
|
|
71
|
+
// ─── .lux file traversal ─────────────────────────────────────────────────────
|
|
52
72
|
|
|
53
|
-
function
|
|
73
|
+
function allLuxFiles(dir) {
|
|
54
74
|
const results = [];
|
|
55
75
|
function walk(d) {
|
|
56
76
|
if (!fs.existsSync(d)) return;
|
|
57
|
-
for (const
|
|
58
|
-
const full = path.join(d,
|
|
59
|
-
if (
|
|
60
|
-
|
|
61
|
-
} else if (entry.isFile() && entry.name.endsWith('.lux')) {
|
|
62
|
-
results.push(full);
|
|
63
|
-
}
|
|
77
|
+
for (const e of fs.readdirSync(d, { withFileTypes: true })) {
|
|
78
|
+
const full = path.join(d, e.name);
|
|
79
|
+
if (e.isDirectory() && !['node_modules', 'depot', '.git'].includes(e.name)) walk(full);
|
|
80
|
+
else if (e.isFile() && e.name.endsWith('.lux')) results.push(full);
|
|
64
81
|
}
|
|
65
82
|
}
|
|
66
83
|
walk(dir);
|
|
67
84
|
return results;
|
|
68
85
|
}
|
|
69
86
|
|
|
70
|
-
// ─── Command:
|
|
87
|
+
// ─── Command: release ─────────────────────────────────────────────────────────
|
|
88
|
+
// Creates a new Luxaura project with unique folder structure + start page
|
|
71
89
|
|
|
72
90
|
program
|
|
73
|
-
.command('
|
|
74
|
-
.description('
|
|
91
|
+
.command('release <name>')
|
|
92
|
+
.description('Bootstrap a new Luxaura project')
|
|
75
93
|
.action(async (name) => {
|
|
76
94
|
banner();
|
|
77
95
|
const projectDir = path.resolve(process.cwd(), name);
|
|
78
96
|
|
|
79
97
|
if (fs.existsSync(projectDir)) {
|
|
80
|
-
error(`
|
|
81
|
-
process.exit(1);
|
|
98
|
+
error(`"${name}" already exists.`); process.exit(1);
|
|
82
99
|
}
|
|
83
100
|
|
|
84
|
-
log(`
|
|
101
|
+
log(`Releasing project: ${chalk.white(name)}`);
|
|
85
102
|
|
|
86
|
-
//
|
|
87
|
-
|
|
103
|
+
// ── Unique folder structure ──────────────────────────────────────────────
|
|
104
|
+
[
|
|
88
105
|
projectDir,
|
|
89
|
-
path.join(projectDir, '
|
|
90
|
-
path.join(projectDir, '
|
|
91
|
-
path.join(projectDir, '
|
|
92
|
-
path.join(projectDir, '
|
|
93
|
-
path.join(projectDir, '
|
|
94
|
-
path.join(projectDir, '
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
// luxaura.config
|
|
106
|
+
path.join(projectDir, 'scenes'), // route entry points (not "pages")
|
|
107
|
+
path.join(projectDir, 'craft'), // components root (not "modules")
|
|
108
|
+
path.join(projectDir, 'craft', 'Ribbon'), // starter Ribbon nav component
|
|
109
|
+
path.join(projectDir, 'services'), // vault/server logic (not "server")
|
|
110
|
+
path.join(projectDir, 'media'), // static assets (not "assets")
|
|
111
|
+
path.join(projectDir, 'depot', 'public'), // build output client (not "dist/client")
|
|
112
|
+
path.join(projectDir, 'depot', 'vault'), // build output server (not "dist/server")
|
|
113
|
+
].forEach(d => fs.mkdirpSync(d));
|
|
114
|
+
|
|
115
|
+
// ── luxaura.config ───────────────────────────────────────────────────────
|
|
99
116
|
fs.writeFileSync(path.join(projectDir, 'luxaura.config'), `
|
|
100
|
-
#
|
|
101
|
-
|
|
102
|
-
app
|
|
117
|
+
# ┌─────────────────────────────────────────┐
|
|
118
|
+
# │ Luxaura Configuration │
|
|
119
|
+
# │ Edit this file to configure your app │
|
|
120
|
+
# └─────────────────────────────────────────┘
|
|
103
121
|
|
|
104
|
-
#
|
|
122
|
+
# App identity
|
|
123
|
+
identity.name: ${name}
|
|
124
|
+
identity.version: 1.0.0
|
|
125
|
+
identity.author: You
|
|
126
|
+
|
|
127
|
+
# Visual theme: light | dark | auto
|
|
105
128
|
theme: light
|
|
106
129
|
|
|
107
|
-
# mode: full | headless
|
|
130
|
+
# Runtime mode: full | headless
|
|
131
|
+
# full → Luxaura handles frontend + vault backend
|
|
132
|
+
# headless → frontend only, vault proxies to external API
|
|
108
133
|
mode: full
|
|
109
134
|
|
|
110
|
-
# Database (optional)
|
|
111
|
-
# db.
|
|
112
|
-
# db.
|
|
135
|
+
# Database connection (optional — uncomment to activate)
|
|
136
|
+
# db.engine: postgres
|
|
137
|
+
# db.link: postgresql://localhost:5432/${name}
|
|
113
138
|
|
|
114
|
-
# Headless proxy (only
|
|
115
|
-
# proxy: http://localhost:8080
|
|
116
|
-
`.trim() + '\n');
|
|
139
|
+
# Headless proxy target (only when mode: headless)
|
|
140
|
+
# proxy.target: http://localhost:8080
|
|
117
141
|
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
# ${name} — Home Page
|
|
121
|
-
|
|
122
|
-
props
|
|
123
|
-
title: String = "Welcome to ${name}"
|
|
124
|
-
|
|
125
|
-
state
|
|
126
|
-
count: 0
|
|
127
|
-
|
|
128
|
-
style
|
|
129
|
-
self
|
|
130
|
-
padding: 8
|
|
131
|
-
background: #ffffff
|
|
132
|
-
|
|
133
|
-
Title
|
|
134
|
-
fontSize: 2xl
|
|
135
|
-
fontWeight: bold
|
|
136
|
-
color: #1a1a2e
|
|
137
|
-
|
|
138
|
-
Action
|
|
139
|
-
padding: 4
|
|
140
|
-
radius: medium
|
|
141
|
-
background: #6c63ff
|
|
142
|
-
color: #ffffff
|
|
143
|
-
cursor: pointer
|
|
144
|
-
|
|
145
|
-
view
|
|
146
|
-
Container
|
|
147
|
-
Title "{title}"
|
|
148
|
-
Text "You have clicked the button {count} times."
|
|
149
|
-
Action "Click Me"
|
|
150
|
-
on click:
|
|
151
|
-
await server.logClick(count)
|
|
142
|
+
# Port for luxaura ignite (dev server)
|
|
143
|
+
port: 3000
|
|
152
144
|
`.trim() + '\n');
|
|
153
145
|
|
|
154
|
-
//
|
|
155
|
-
fs.writeFileSync(path.join(projectDir, '
|
|
156
|
-
# Reusable Navbar Component
|
|
146
|
+
// ── scenes/index.lux — The start page (rich how-to content) ─────────────
|
|
147
|
+
fs.writeFileSync(path.join(projectDir, 'scenes', 'index.lux'), buildStartPage(name));
|
|
157
148
|
|
|
158
|
-
|
|
149
|
+
// ── craft/Ribbon/Ribbon.lux — starter nav component ─────────────────────
|
|
150
|
+
fs.writeFileSync(path.join(projectDir, 'craft', 'Ribbon', 'Ribbon.lux'), `
|
|
151
|
+
# Ribbon Navigation Component
|
|
152
|
+
# Modify this component to build your own navigation bar.
|
|
153
|
+
|
|
154
|
+
receive
|
|
159
155
|
brand: String = "${name}"
|
|
160
156
|
|
|
161
|
-
|
|
157
|
+
paint
|
|
162
158
|
self
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
159
|
+
breathe: 4
|
|
160
|
+
fill: #0d0d1a
|
|
161
|
+
glow: whisper
|
|
162
|
+
|
|
163
|
+
Headline
|
|
164
|
+
ink: #ffffff
|
|
165
|
+
size: large
|
|
166
|
+
weight: strong
|
|
167
|
+
|
|
168
|
+
Trigger
|
|
169
|
+
curve: pill
|
|
170
|
+
breathe: 3
|
|
171
|
+
fill: #6c63ff
|
|
172
|
+
ink: #ffffff
|
|
173
|
+
pointer: true
|
|
174
|
+
|
|
175
|
+
canvas
|
|
176
|
+
Ribbon
|
|
177
|
+
Strip
|
|
178
|
+
Headline "<<brand>>"
|
|
179
|
+
Strip
|
|
180
|
+
Trigger "Home"
|
|
181
|
+
Trigger "Docs"
|
|
182
|
+
Trigger "About"
|
|
180
183
|
`.trim() + '\n');
|
|
181
184
|
|
|
182
|
-
//
|
|
183
|
-
fs.writeFileSync(path.join(projectDir, '
|
|
185
|
+
// ── craft/Ribbon/Ribbon.paint.css — component-scoped styles ─────────────
|
|
186
|
+
fs.writeFileSync(path.join(projectDir, 'craft', 'Ribbon', 'Ribbon.paint.css'),
|
|
187
|
+
`/* Ribbon component — scoped paint styles */\n/* Add CSS here for fine-grained control */\n`);
|
|
188
|
+
|
|
189
|
+
// ── services/index.js — starter vault service ────────────────────────────
|
|
190
|
+
fs.writeFileSync(path.join(projectDir, 'services', 'index.js'), `
|
|
184
191
|
'use strict';
|
|
185
|
-
|
|
192
|
+
/**
|
|
193
|
+
* Luxaura Vault Service
|
|
194
|
+
* Functions defined here run ONLY on the server.
|
|
195
|
+
* They are called from .lux files via: await vault.functionName(args)
|
|
196
|
+
*/
|
|
186
197
|
|
|
187
|
-
async function
|
|
188
|
-
|
|
189
|
-
return { ok: true, count };
|
|
198
|
+
async function ping() {
|
|
199
|
+
return { ok: true, message: 'Vault is alive', time: new Date().toISOString() };
|
|
190
200
|
}
|
|
191
201
|
|
|
192
|
-
module.exports = {
|
|
202
|
+
module.exports = { ping };
|
|
193
203
|
`);
|
|
194
204
|
|
|
195
|
-
// package.json
|
|
205
|
+
// ── package.json ─────────────────────────────────────────────────────────
|
|
196
206
|
fs.writeFileSync(path.join(projectDir, 'package.json'), JSON.stringify({
|
|
197
207
|
name,
|
|
198
208
|
version: '1.0.0',
|
|
199
|
-
description: `${name} —
|
|
209
|
+
description: `${name} — crafted with Luxaura`,
|
|
200
210
|
scripts: {
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
},
|
|
204
|
-
dependencies: {
|
|
205
|
-
luxaura: `^${VERSION}`,
|
|
211
|
+
ignite: 'luxaura ignite',
|
|
212
|
+
forge: 'luxaura forge',
|
|
206
213
|
},
|
|
214
|
+
dependencies: { luxaura: `^${VERSION}` },
|
|
207
215
|
}, null, 2));
|
|
208
216
|
|
|
209
|
-
// .gitignore
|
|
210
|
-
fs.writeFileSync(path.join(projectDir, '.gitignore'), `node_modules/\
|
|
217
|
+
// ── .gitignore ───────────────────────────────────────────────────────────
|
|
218
|
+
fs.writeFileSync(path.join(projectDir, '.gitignore'), `node_modules/\ndepot/\n.DS_Store\n*.log\n`);
|
|
211
219
|
|
|
212
|
-
// Copy UI kit
|
|
213
|
-
const
|
|
214
|
-
const
|
|
215
|
-
if (fs.existsSync(path.join(
|
|
216
|
-
fs.copySync(path.join(
|
|
217
|
-
fs.copySync(path.join(
|
|
220
|
+
// ── Copy UI kit to depot/public ──────────────────────────────────────────
|
|
221
|
+
const kitSrc = path.join(__dirname, '..', 'ui-kit');
|
|
222
|
+
const kitDst = path.join(projectDir, 'depot', 'public');
|
|
223
|
+
if (fs.existsSync(path.join(kitSrc, 'luxaura.min.css'))) {
|
|
224
|
+
fs.copySync(path.join(kitSrc, 'luxaura.min.css'), path.join(kitDst, 'luxaura.min.css'));
|
|
225
|
+
fs.copySync(path.join(kitSrc, 'luxaura.min.js'), path.join(kitDst, 'luxaura.min.js'));
|
|
218
226
|
}
|
|
219
227
|
|
|
220
|
-
success(`Project "${name}"
|
|
221
|
-
|
|
222
|
-
info(`
|
|
228
|
+
success(`Project "${chalk.white(name)}" released!`);
|
|
229
|
+
dim('');
|
|
230
|
+
info(` ${chalk.white('cd ' + name)}`);
|
|
231
|
+
info(` ${chalk.white('luxaura ignite')} ${chalk.gray('→ start dev server')}`);
|
|
232
|
+
info(` ${chalk.white('luxaura help_me')} ${chalk.gray('→ see all commands')}`);
|
|
233
|
+
dim('');
|
|
223
234
|
});
|
|
224
235
|
|
|
225
|
-
// ───
|
|
236
|
+
// ─── Start page builder ───────────────────────────────────────────────────────
|
|
237
|
+
|
|
238
|
+
function buildStartPage(name) {
|
|
239
|
+
return `
|
|
240
|
+
# ${name} — Start Scene
|
|
241
|
+
# This is your project's home. Edit it freely.
|
|
242
|
+
# Run: luxaura help_me to see all commands.
|
|
243
|
+
|
|
244
|
+
receive
|
|
245
|
+
appName: String = "${name}"
|
|
246
|
+
|
|
247
|
+
signal
|
|
248
|
+
activeSection: "welcome"
|
|
249
|
+
|
|
250
|
+
paint
|
|
251
|
+
self
|
|
252
|
+
fill: #0d0d1a
|
|
253
|
+
breathe: 0
|
|
254
|
+
|
|
255
|
+
Headline
|
|
256
|
+
ink: #ffffff
|
|
257
|
+
weight: heavy
|
|
258
|
+
|
|
259
|
+
Body
|
|
260
|
+
ink: #b0afd0
|
|
261
|
+
|
|
262
|
+
Trigger
|
|
263
|
+
curve: pill
|
|
264
|
+
glow: soft
|
|
265
|
+
pointer: true
|
|
266
|
+
|
|
267
|
+
Plate
|
|
268
|
+
fill: #1a1a2e
|
|
269
|
+
curve: round
|
|
270
|
+
glow: whisper
|
|
271
|
+
breathe: 6
|
|
272
|
+
|
|
273
|
+
Snippet
|
|
274
|
+
fill: #0a0920
|
|
275
|
+
ink: #a8a3ff
|
|
276
|
+
curve: gentle
|
|
277
|
+
|
|
278
|
+
canvas
|
|
279
|
+
Frame
|
|
280
|
+
Ribbon
|
|
281
|
+
Strip
|
|
282
|
+
Headline "<<appName>>"
|
|
283
|
+
Mark "v1.0"
|
|
284
|
+
Strip
|
|
285
|
+
Trigger "Docs"
|
|
286
|
+
Trigger "GitHub"
|
|
287
|
+
|
|
288
|
+
Scene
|
|
289
|
+
Stack
|
|
290
|
+
|
|
291
|
+
Frame
|
|
292
|
+
Headline "Welcome to <<appName>>"
|
|
293
|
+
Body "Your Luxaura project is alive. This is your start scene — edit scenes/index.lux to make it yours."
|
|
294
|
+
Strip
|
|
295
|
+
Trigger "Read the Docs"
|
|
296
|
+
when click:
|
|
297
|
+
Trigger "View on GitHub"
|
|
298
|
+
when click:
|
|
299
|
+
|
|
300
|
+
Splitter
|
|
301
|
+
|
|
302
|
+
Plate
|
|
303
|
+
Headline "Luxaura Quick Reference"
|
|
304
|
+
Body "Everything is different by design. Here is what you need to know."
|
|
305
|
+
|
|
306
|
+
Mosaic
|
|
307
|
+
|
|
308
|
+
Plate
|
|
309
|
+
Headline "Project structure"
|
|
310
|
+
Roster
|
|
311
|
+
Entry
|
|
312
|
+
Snippet "/scenes"
|
|
313
|
+
Body "Your routes live here. index.lux is this page."
|
|
314
|
+
Entry
|
|
315
|
+
Snippet "/craft"
|
|
316
|
+
Body "Your components. Each gets its own folder."
|
|
317
|
+
Entry
|
|
318
|
+
Snippet "/services"
|
|
319
|
+
Body "Vault logic — server-only, never exposed."
|
|
320
|
+
Entry
|
|
321
|
+
Snippet "/media"
|
|
322
|
+
Body "Images, fonts, static files."
|
|
323
|
+
Entry
|
|
324
|
+
Snippet "/depot"
|
|
325
|
+
Body "Build output. Never commit this."
|
|
326
|
+
|
|
327
|
+
Plate
|
|
328
|
+
Headline "CLI Commands"
|
|
329
|
+
Roster
|
|
330
|
+
Entry
|
|
331
|
+
Snippet "luxaura ignite"
|
|
332
|
+
Body "Start the dev server with live reload."
|
|
333
|
+
Entry
|
|
334
|
+
Snippet "luxaura forge"
|
|
335
|
+
Body "Build for production."
|
|
336
|
+
Entry
|
|
337
|
+
Snippet "luxaura component MyCard"
|
|
338
|
+
Body "Create a new component in /craft/MyCard/."
|
|
339
|
+
Entry
|
|
340
|
+
Snippet "luxaura service UserService"
|
|
341
|
+
Body "Create a vault service in /services/."
|
|
342
|
+
Entry
|
|
343
|
+
Snippet "luxaura module SharedLogic"
|
|
344
|
+
Body "Create a shared module."
|
|
345
|
+
Entry
|
|
346
|
+
Snippet "luxaura bring tailwindcss"
|
|
347
|
+
Body "Pull in an external library."
|
|
348
|
+
Entry
|
|
349
|
+
Snippet "luxaura polish"
|
|
350
|
+
Body "Auto-format all .lux files."
|
|
351
|
+
Entry
|
|
352
|
+
Snippet "luxaura shield"
|
|
353
|
+
Body "Scan for security issues."
|
|
354
|
+
Entry
|
|
355
|
+
Snippet "luxaura help_me"
|
|
356
|
+
Body "Full interactive CLI guide."
|
|
357
|
+
|
|
358
|
+
Plate
|
|
359
|
+
Headline ".lux Syntax"
|
|
360
|
+
Roster
|
|
361
|
+
Entry
|
|
362
|
+
Snippet "receive"
|
|
363
|
+
Body "Inputs passed in from outside."
|
|
364
|
+
Entry
|
|
365
|
+
Snippet "signal"
|
|
366
|
+
Body "Reactive data inside this scene."
|
|
367
|
+
Entry
|
|
368
|
+
Snippet "vault"
|
|
369
|
+
Body "Server-only logic. Never reaches browser."
|
|
370
|
+
Entry
|
|
371
|
+
Snippet "paint"
|
|
372
|
+
Body "Styles using smart token names."
|
|
373
|
+
Entry
|
|
374
|
+
Snippet "canvas"
|
|
375
|
+
Body "Your UI tree using Luxaura nodes."
|
|
376
|
+
Entry
|
|
377
|
+
Snippet "<<varName>>"
|
|
378
|
+
Body "Bind a signal or receive value."
|
|
379
|
+
Entry
|
|
380
|
+
Snippet "when click:"
|
|
381
|
+
Body "Event handler on any node."
|
|
382
|
+
Entry
|
|
383
|
+
Snippet "await vault.myAction()"
|
|
384
|
+
Body "Call a server vault action."
|
|
385
|
+
|
|
386
|
+
Plate
|
|
387
|
+
Headline "Paint Tokens"
|
|
388
|
+
Roster
|
|
389
|
+
Entry
|
|
390
|
+
Snippet "breathe: 4"
|
|
391
|
+
Body "Padding — 4 × 4px = 16px."
|
|
392
|
+
Entry
|
|
393
|
+
Snippet "push: 4"
|
|
394
|
+
Body "Margin."
|
|
395
|
+
Entry
|
|
396
|
+
Snippet "glow: soft"
|
|
397
|
+
Body "Box shadow."
|
|
398
|
+
Entry
|
|
399
|
+
Snippet "curve: smooth"
|
|
400
|
+
Body "Border radius — 8px."
|
|
401
|
+
Entry
|
|
402
|
+
Snippet "ink: #fff"
|
|
403
|
+
Body "Text color."
|
|
404
|
+
Entry
|
|
405
|
+
Snippet "fill: #000"
|
|
406
|
+
Body "Background color."
|
|
407
|
+
Entry
|
|
408
|
+
Snippet "size: titan"
|
|
409
|
+
Body "Font size — 3rem."
|
|
410
|
+
Entry
|
|
411
|
+
Snippet "weight: heavy"
|
|
412
|
+
Body "Font weight — 900."
|
|
413
|
+
|
|
414
|
+
Splitter
|
|
415
|
+
|
|
416
|
+
Anchor
|
|
417
|
+
Body "Built with Luxaura — the framework that does things differently."
|
|
418
|
+
`.trim() + '\n';
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// ─── Command: ignite (was: run) ───────────────────────────────────────────────
|
|
226
422
|
|
|
227
423
|
program
|
|
228
|
-
.command('
|
|
229
|
-
.description('Start
|
|
230
|
-
.option('-p, --port <port>', 'Port
|
|
424
|
+
.command('ignite')
|
|
425
|
+
.description('Start the Luxaura dev server with live reload')
|
|
426
|
+
.option('-p, --port <port>', 'Port to listen on', '3000')
|
|
231
427
|
.action(async (opts) => {
|
|
232
428
|
banner();
|
|
233
429
|
const rootDir = process.cwd();
|
|
234
|
-
const port
|
|
430
|
+
const port = parseInt(opts.port, 10);
|
|
235
431
|
|
|
236
|
-
log('
|
|
432
|
+
log('Forging scenes and craft...');
|
|
237
433
|
await buildAll(rootDir, { dev: true });
|
|
238
434
|
|
|
239
|
-
const
|
|
240
|
-
const
|
|
241
|
-
success(`Dev server
|
|
242
|
-
info('
|
|
435
|
+
const server = new VaultServer({ port, rootDir });
|
|
436
|
+
const live = await server.start();
|
|
437
|
+
success(`Dev server ignited at ${chalk.white(`http://localhost:${live}`)}`);
|
|
438
|
+
info('Live reload active. Watching /scenes, /craft, /services...\n');
|
|
243
439
|
|
|
244
|
-
|
|
245
|
-
_injectHMR(rootDir, actualPort);
|
|
440
|
+
_injectLiveReload(rootDir, live);
|
|
246
441
|
|
|
247
442
|
const watcher = chokidar.watch(
|
|
248
|
-
[
|
|
443
|
+
[
|
|
444
|
+
path.join(rootDir, 'scenes'),
|
|
445
|
+
path.join(rootDir, 'craft'),
|
|
446
|
+
path.join(rootDir, 'services'),
|
|
447
|
+
],
|
|
249
448
|
{ ignoreInitial: true }
|
|
250
449
|
);
|
|
251
450
|
|
|
252
|
-
watcher.on('change', async (
|
|
253
|
-
log(`Changed: ${chalk.yellow(path.relative(rootDir,
|
|
451
|
+
watcher.on('change', async (fp) => {
|
|
452
|
+
log(`Changed: ${chalk.yellow(path.relative(rootDir, fp))}`);
|
|
254
453
|
try {
|
|
255
|
-
await buildFile(
|
|
256
|
-
|
|
257
|
-
success('Rebuilt
|
|
258
|
-
} catch (e) {
|
|
259
|
-
error(`Compile error: ${e.message}`);
|
|
260
|
-
}
|
|
454
|
+
if (fp.endsWith('.lux')) await buildFile(fp, rootDir, { dev: true });
|
|
455
|
+
server.triggerHMR(path.relative(rootDir, fp));
|
|
456
|
+
success('Rebuilt.');
|
|
457
|
+
} catch (e) { error(`Build error: ${e.message}`); }
|
|
261
458
|
});
|
|
262
459
|
|
|
263
|
-
watcher.on('add', async (
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
460
|
+
watcher.on('add', async (fp) => {
|
|
461
|
+
if (!fp.endsWith('.lux')) return;
|
|
462
|
+
log(`New: ${chalk.yellow(path.relative(rootDir, fp))}`);
|
|
463
|
+
await buildFile(fp, rootDir, { dev: true });
|
|
464
|
+
server.triggerHMR('new-file');
|
|
267
465
|
});
|
|
268
466
|
});
|
|
269
467
|
|
|
270
|
-
function
|
|
271
|
-
const
|
|
272
|
-
if (!fs.existsSync(
|
|
273
|
-
let html = fs.readFileSync(
|
|
274
|
-
if (html.includes('
|
|
275
|
-
const
|
|
276
|
-
<script id="__lux_hmr__">
|
|
468
|
+
function _injectLiveReload(rootDir, port) {
|
|
469
|
+
const htmlPath = path.join(rootDir, 'depot', 'public', 'index.html');
|
|
470
|
+
if (!fs.existsSync(htmlPath)) return;
|
|
471
|
+
let html = fs.readFileSync(htmlPath, 'utf8');
|
|
472
|
+
if (html.includes('__lux_live__')) return;
|
|
473
|
+
const script = `\n<script id="__lux_live__">
|
|
277
474
|
(function(){
|
|
278
|
-
|
|
279
|
-
ws.onmessage = function(e)
|
|
280
|
-
|
|
281
|
-
if (
|
|
282
|
-
console.log('[Luxaura HMR] Reloading:', msg.file);
|
|
283
|
-
location.reload();
|
|
284
|
-
}
|
|
285
|
-
};
|
|
286
|
-
ws.onclose = function() {
|
|
287
|
-
console.log('[Luxaura HMR] Connection lost. Reconnecting...');
|
|
288
|
-
setTimeout(() => location.reload(), 2000);
|
|
475
|
+
var ws = new WebSocket('ws://localhost:${port}');
|
|
476
|
+
ws.onmessage = function(e){
|
|
477
|
+
var m = JSON.parse(e.data);
|
|
478
|
+
if (m.type === 'hmr') { console.log('[Luxaura Live]', m.file); location.reload(); }
|
|
289
479
|
};
|
|
480
|
+
ws.onclose = function(){ setTimeout(function(){ location.reload(); }, 2000); };
|
|
290
481
|
})();
|
|
291
482
|
</script>`;
|
|
292
|
-
html = html.replace('</body>',
|
|
293
|
-
fs.writeFileSync(
|
|
483
|
+
html = html.replace('</body>', script + '</body>');
|
|
484
|
+
fs.writeFileSync(htmlPath, html);
|
|
294
485
|
}
|
|
295
486
|
|
|
296
|
-
// ─── Command: build
|
|
487
|
+
// ─── Command: forge (was: build) ─────────────────────────────────────────────
|
|
297
488
|
|
|
298
489
|
program
|
|
299
|
-
.command('
|
|
300
|
-
.description('
|
|
490
|
+
.command('forge')
|
|
491
|
+
.description('Forge the production build (splits public + vault)')
|
|
301
492
|
.action(async () => {
|
|
302
493
|
banner();
|
|
303
494
|
const rootDir = process.cwd();
|
|
304
|
-
log('
|
|
305
|
-
|
|
495
|
+
log('Forging production build...');
|
|
306
496
|
const start = Date.now();
|
|
307
497
|
const stats = await buildAll(rootDir, { dev: false });
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
info(`
|
|
311
|
-
|
|
312
|
-
info(` Files compiled: ${stats.count}`);
|
|
313
|
-
if (stats.errors.length) {
|
|
314
|
-
stats.errors.forEach(e => error(e));
|
|
315
|
-
}
|
|
498
|
+
success(`Forged in ${Date.now() - start}ms — ${stats.count} file(s)`);
|
|
499
|
+
info(` Public assets → ${chalk.white('depot/public/')}`);
|
|
500
|
+
info(` Vault modules → ${chalk.white('depot/vault/')}`);
|
|
501
|
+
if (stats.errors.length) stats.errors.forEach(e => error(e));
|
|
316
502
|
});
|
|
317
503
|
|
|
318
|
-
// ─── Command: install
|
|
504
|
+
// ─── Command: bring (was: install) ───────────────────────────────────────────
|
|
319
505
|
|
|
320
506
|
program
|
|
321
|
-
.command('
|
|
322
|
-
.description('
|
|
323
|
-
.action(
|
|
507
|
+
.command('bring <lib>')
|
|
508
|
+
.description('Bring in an external library and wire it into the build')
|
|
509
|
+
.action((lib) => {
|
|
324
510
|
banner();
|
|
325
|
-
log(`
|
|
511
|
+
log(`Bringing in: ${chalk.yellow(lib)}`);
|
|
326
512
|
try {
|
|
327
513
|
execSync(`npm install ${lib}`, { stdio: 'inherit', cwd: process.cwd() });
|
|
328
|
-
success(`${lib}
|
|
329
|
-
info('
|
|
330
|
-
} catch (e) {
|
|
331
|
-
error(`npm install failed: ${e.message}`);
|
|
332
|
-
}
|
|
514
|
+
success(`${lib} is now available.`);
|
|
515
|
+
info('Use its classes directly in your canvas via the class: attribute.');
|
|
516
|
+
} catch (e) { error(`npm install failed: ${e.message}`); }
|
|
333
517
|
});
|
|
334
518
|
|
|
335
|
-
// ─── Command:
|
|
519
|
+
// ─── Command: component ───────────────────────────────────────────────────────
|
|
520
|
+
// Creates /craft/<Name>/<Name>.lux + <Name>.paint.css + index.js
|
|
521
|
+
|
|
522
|
+
program
|
|
523
|
+
.command('component <name>')
|
|
524
|
+
.description('Craft a new UI component in /craft/<Name>/')
|
|
525
|
+
.action((name) => {
|
|
526
|
+
banner();
|
|
527
|
+
const rootDir = process.cwd();
|
|
528
|
+
const compDir = path.join(rootDir, 'craft', name);
|
|
529
|
+
|
|
530
|
+
if (fs.existsSync(compDir)) {
|
|
531
|
+
warn(`Component "${name}" already exists at craft/${name}/`);
|
|
532
|
+
process.exit(0);
|
|
533
|
+
}
|
|
534
|
+
|
|
535
|
+
fs.mkdirpSync(compDir);
|
|
336
536
|
|
|
337
|
-
|
|
338
|
-
|
|
537
|
+
// ── <Name>.lux — component with a styled big title ───────────────────────
|
|
538
|
+
fs.writeFileSync(path.join(compDir, `${name}.lux`), `
|
|
339
539
|
# ${name} Component
|
|
540
|
+
# Edit this file to build your component.
|
|
340
541
|
|
|
341
|
-
|
|
342
|
-
label: String = "${name}"
|
|
542
|
+
receive
|
|
543
|
+
label: String = "I'm ${name} component !!"
|
|
343
544
|
|
|
344
|
-
|
|
545
|
+
signal
|
|
345
546
|
active: false
|
|
346
547
|
|
|
347
|
-
|
|
548
|
+
paint
|
|
348
549
|
self
|
|
349
|
-
|
|
350
|
-
|
|
351
|
-
|
|
550
|
+
breathe: 8
|
|
551
|
+
fill: #0d0d1a
|
|
552
|
+
glow: soft
|
|
553
|
+
curve: round
|
|
554
|
+
|
|
555
|
+
Headline
|
|
556
|
+
ink: #ffffff
|
|
557
|
+
size: titan
|
|
558
|
+
weight: heavy
|
|
559
|
+
align: center
|
|
560
|
+
|
|
561
|
+
Body
|
|
562
|
+
ink: #a8a3ff
|
|
563
|
+
size: large
|
|
564
|
+
align: center
|
|
565
|
+
push: 4
|
|
566
|
+
|
|
567
|
+
Mark
|
|
568
|
+
fill: #6c63ff
|
|
569
|
+
ink: #ffffff
|
|
570
|
+
curve: pill
|
|
571
|
+
push: 2
|
|
572
|
+
|
|
573
|
+
canvas
|
|
574
|
+
Frame
|
|
575
|
+
Stack
|
|
576
|
+
Headline "<<label>>"
|
|
577
|
+
Body "I live in craft/${name}/${name}.lux — edit me!"
|
|
578
|
+
Strip
|
|
579
|
+
Mark "Luxaura Component"
|
|
580
|
+
Mark "<<active>>"
|
|
581
|
+
`.trim() + '\n');
|
|
352
582
|
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
583
|
+
// ── <Name>.paint.css — scoped paint styles ───────────────────────────────
|
|
584
|
+
fs.writeFileSync(path.join(compDir, `${name}.paint.css`), `
|
|
585
|
+
/* ${name} — Component-scoped paint styles */
|
|
586
|
+
/* These styles apply only to this component. */
|
|
587
|
+
/* Use .lux-* classes from luxaura.min.css or write your own. */
|
|
358
588
|
|
|
359
|
-
|
|
360
|
-
|
|
589
|
+
.lux-scene-${name.toLowerCase()} {
|
|
590
|
+
/* Component root styles */
|
|
591
|
+
}
|
|
592
|
+
`.trim() + '\n');
|
|
361
593
|
|
|
362
|
-
|
|
363
|
-
|
|
594
|
+
// ── index.js — optional JS bridge ───────────────────────────────────────
|
|
595
|
+
fs.writeFileSync(path.join(compDir, 'index.js'), `
|
|
596
|
+
/**
|
|
597
|
+
* ${name} — Optional JS bridge
|
|
598
|
+
* Use this file to export helpers, constants, or sub-components.
|
|
599
|
+
* The .lux file handles the UI — this is for shared logic only.
|
|
600
|
+
*/
|
|
364
601
|
|
|
365
|
-
|
|
366
|
-
|
|
602
|
+
module.exports = {
|
|
603
|
+
name: '${name}',
|
|
604
|
+
version: '1.0.0',
|
|
605
|
+
};
|
|
606
|
+
`);
|
|
367
607
|
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
608
|
+
success(`Component "${chalk.white(name)}" crafted at ${chalk.white(`craft/${name}/`)}`);
|
|
609
|
+
info(` ${chalk.white(`craft/${name}/${name}.lux`)} ← the component canvas`);
|
|
610
|
+
info(` ${chalk.white(`craft/${name}/${name}.paint.css`)} ← scoped styles`);
|
|
611
|
+
info(` ${chalk.white(`craft/${name}/index.js`)} ← optional JS bridge`);
|
|
612
|
+
});
|
|
371
613
|
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
Title "{title}"
|
|
375
|
-
Text "This is the ${name} page."
|
|
376
|
-
`.trim() + '\n',
|
|
614
|
+
// ─── Command: service ────────────────────────────────────────────────────────
|
|
615
|
+
// Creates /services/<Name>.service.js — vault-only, never reaches browser
|
|
377
616
|
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
617
|
+
program
|
|
618
|
+
.command('service <name>')
|
|
619
|
+
.description('Create a vault service in /services/ (server-only)')
|
|
620
|
+
.action((name) => {
|
|
621
|
+
banner();
|
|
622
|
+
const rootDir = process.cwd();
|
|
623
|
+
const servicesDir = path.join(rootDir, 'services');
|
|
624
|
+
fs.mkdirpSync(servicesDir);
|
|
381
625
|
|
|
382
|
-
|
|
383
|
-
|
|
626
|
+
const filePath = path.join(servicesDir, `${name}.service.js`);
|
|
627
|
+
if (fs.existsSync(filePath)) {
|
|
628
|
+
warn(`Service "${name}" already exists.`); process.exit(0);
|
|
629
|
+
}
|
|
384
630
|
|
|
385
|
-
|
|
386
|
-
|
|
631
|
+
fs.writeFileSync(filePath, `
|
|
632
|
+
/**
|
|
633
|
+
* ${name} — Luxaura Vault Service
|
|
634
|
+
*
|
|
635
|
+
* ┌─────────────────────────────────────────────────────┐
|
|
636
|
+
* │ VAULT — This file NEVER reaches the browser. │
|
|
637
|
+
* │ All functions here run server-side only. │
|
|
638
|
+
* │ Call them from .lux files: await vault.action() │
|
|
639
|
+
* └─────────────────────────────────────────────────────┘
|
|
640
|
+
*
|
|
641
|
+
* Syntax in .lux files:
|
|
642
|
+
*
|
|
643
|
+
* vault
|
|
644
|
+
* pull db from "luxaura/db"
|
|
645
|
+
* action get${name}(id):
|
|
646
|
+
* return db.query("SELECT * FROM ${name.toLowerCase()}s WHERE id = ?", [id])
|
|
647
|
+
*/
|
|
648
|
+
'use strict';
|
|
387
649
|
|
|
388
|
-
|
|
389
|
-
|
|
650
|
+
/**
|
|
651
|
+
* get${name} — Fetch a ${name} by id
|
|
652
|
+
*/
|
|
653
|
+
async function get${name}(id) {
|
|
654
|
+
// TODO: implement
|
|
655
|
+
return { id, name: '${name}', fetched: new Date().toISOString() };
|
|
656
|
+
}
|
|
390
657
|
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
}
|
|
658
|
+
/**
|
|
659
|
+
* create${name} — Create a new ${name}
|
|
660
|
+
*/
|
|
661
|
+
async function create${name}(data) {
|
|
662
|
+
// TODO: implement
|
|
663
|
+
return { ok: true, data };
|
|
664
|
+
}
|
|
665
|
+
|
|
666
|
+
/**
|
|
667
|
+
* update${name} — Update a ${name}
|
|
668
|
+
*/
|
|
669
|
+
async function update${name}(id, data) {
|
|
670
|
+
// TODO: implement
|
|
671
|
+
return { ok: true, id, data };
|
|
672
|
+
}
|
|
673
|
+
|
|
674
|
+
/**
|
|
675
|
+
* delete${name} — Delete a ${name}
|
|
676
|
+
*/
|
|
677
|
+
async function delete${name}(id) {
|
|
678
|
+
// TODO: implement
|
|
679
|
+
return { ok: true, id };
|
|
680
|
+
}
|
|
681
|
+
|
|
682
|
+
module.exports = { get${name}, create${name}, update${name}, delete${name} };
|
|
683
|
+
`);
|
|
684
|
+
|
|
685
|
+
success(`Service "${chalk.white(name)}" created at ${chalk.white(`services/${name}.service.js`)}`);
|
|
686
|
+
info('Call its actions from .lux vault blocks:');
|
|
687
|
+
info(` ${chalk.white(`await vault.get${name}(id)`)}`);
|
|
688
|
+
});
|
|
689
|
+
|
|
690
|
+
// ─── Command: module ─────────────────────────────────────────────────────────
|
|
691
|
+
// Creates a shared logic module — used by multiple scenes or components
|
|
395
692
|
|
|
396
693
|
program
|
|
397
|
-
.command('
|
|
398
|
-
.
|
|
399
|
-
.
|
|
400
|
-
.action((type, name) => {
|
|
694
|
+
.command('module <name>')
|
|
695
|
+
.description('Create a shared module in /craft/<Name>/')
|
|
696
|
+
.action((name) => {
|
|
401
697
|
banner();
|
|
402
|
-
const rootDir
|
|
403
|
-
const
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
process.exit(
|
|
698
|
+
const rootDir = process.cwd();
|
|
699
|
+
const modDir = path.join(rootDir, 'craft', name);
|
|
700
|
+
|
|
701
|
+
if (fs.existsSync(modDir)) {
|
|
702
|
+
warn(`Module "${name}" already exists.`); process.exit(0);
|
|
407
703
|
}
|
|
408
704
|
|
|
409
|
-
|
|
410
|
-
const dir = path.join(rootDir, dirs[type]);
|
|
411
|
-
fs.mkdirpSync(dir);
|
|
705
|
+
fs.mkdirpSync(modDir);
|
|
412
706
|
|
|
413
|
-
|
|
414
|
-
|
|
415
|
-
|
|
416
|
-
|
|
417
|
-
|
|
707
|
+
// A module is a .lux scene + a JS utility file — shared, not a route
|
|
708
|
+
fs.writeFileSync(path.join(modDir, `${name}.lux`), `
|
|
709
|
+
# ${name} — Shared Module
|
|
710
|
+
# This module can be composed into any scene or component.
|
|
711
|
+
|
|
712
|
+
receive
|
|
713
|
+
title: String = "${name} Module"
|
|
714
|
+
|
|
715
|
+
signal
|
|
716
|
+
ready: true
|
|
717
|
+
|
|
718
|
+
paint
|
|
719
|
+
self
|
|
720
|
+
breathe: 6
|
|
721
|
+
fill: #1a1a2e
|
|
722
|
+
curve: smooth
|
|
723
|
+
|
|
724
|
+
Headline
|
|
725
|
+
ink: #6c63ff
|
|
726
|
+
size: xlarge
|
|
727
|
+
weight: strong
|
|
728
|
+
|
|
729
|
+
canvas
|
|
730
|
+
Frame
|
|
731
|
+
Stack
|
|
732
|
+
Headline "<<title>>"
|
|
733
|
+
Body "I'm the ${name} module — compose me anywhere."
|
|
734
|
+
`.trim() + '\n');
|
|
735
|
+
|
|
736
|
+
fs.writeFileSync(path.join(modDir, `${name}.utils.js`), `
|
|
737
|
+
/**
|
|
738
|
+
* ${name} Module — Shared utilities
|
|
739
|
+
* Import these helpers into any service or scene.
|
|
740
|
+
*/
|
|
741
|
+
'use strict';
|
|
742
|
+
|
|
743
|
+
function format${name}(data) {
|
|
744
|
+
return { ...data, formatted: true, module: '${name}' };
|
|
745
|
+
}
|
|
746
|
+
|
|
747
|
+
module.exports = { format${name} };
|
|
748
|
+
`);
|
|
749
|
+
|
|
750
|
+
success(`Module "${chalk.white(name)}" created at ${chalk.white(`craft/${name}/`)}`);
|
|
751
|
+
info(` ${chalk.white(`craft/${name}/${name}.lux`)} ← composable scene`);
|
|
752
|
+
info(` ${chalk.white(`craft/${name}/${name}.utils.js`)} ← shared utilities`);
|
|
753
|
+
});
|
|
754
|
+
|
|
755
|
+
// ─── Command: help_me ────────────────────────────────────────────────────────
|
|
756
|
+
|
|
757
|
+
program
|
|
758
|
+
.command('help_me')
|
|
759
|
+
.description('Interactive CLI guide — everything Luxaura')
|
|
760
|
+
.action(() => {
|
|
761
|
+
banner();
|
|
418
762
|
|
|
419
|
-
|
|
420
|
-
|
|
763
|
+
console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════'));
|
|
764
|
+
console.log(chalk.hex('#a8a3ff').bold(' LUXAURA CLI — Complete Reference'));
|
|
765
|
+
console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
|
|
766
|
+
|
|
767
|
+
const section = (title) => {
|
|
768
|
+
console.log(chalk.hex('#6c63ff').bold(` ▸ ${title}`));
|
|
769
|
+
};
|
|
770
|
+
const cmd = (c, d) => {
|
|
771
|
+
console.log(` ${chalk.white(c.padEnd(38))} ${chalk.gray(d)}`);
|
|
772
|
+
};
|
|
773
|
+
const note = (t) => console.log(chalk.gray(` ${t}`));
|
|
774
|
+
const br = () => console.log('');
|
|
775
|
+
|
|
776
|
+
section('PROJECT');
|
|
777
|
+
cmd('luxaura release <name>', 'Bootstrap a new project');
|
|
778
|
+
note('Creates /scenes /craft /services /media /depot');
|
|
779
|
+
note('Generates a rich start scene with full docs at scenes/index.lux');
|
|
780
|
+
br();
|
|
781
|
+
|
|
782
|
+
section('DEVELOPMENT');
|
|
783
|
+
cmd('luxaura ignite', 'Start dev server + live reload (port 3000)');
|
|
784
|
+
cmd('luxaura ignite --port 8080', 'Start on a custom port');
|
|
785
|
+
cmd('luxaura forge', 'Build for production → depot/public + depot/vault');
|
|
786
|
+
br();
|
|
787
|
+
|
|
788
|
+
section('SCAFFOLDING');
|
|
789
|
+
cmd('luxaura component <Name>', 'Create a component → craft/<Name>/');
|
|
790
|
+
note('Generates: <Name>.lux + <Name>.paint.css + index.js');
|
|
791
|
+
cmd('luxaura service <Name>', 'Create a vault service → services/<Name>.service.js');
|
|
792
|
+
note('Vault services run server-side ONLY — never exposed to browser');
|
|
793
|
+
cmd('luxaura module <Name>', 'Create a shared module → craft/<Name>/');
|
|
794
|
+
note('Modules are composable — use them across scenes and components');
|
|
795
|
+
br();
|
|
796
|
+
|
|
797
|
+
section('LIBRARIES');
|
|
798
|
+
cmd('luxaura bring <lib>', 'Pull in an npm library (e.g. luxaura bring gsap)');
|
|
799
|
+
note('Wires the library into your build automatically');
|
|
800
|
+
br();
|
|
801
|
+
|
|
802
|
+
section('QUALITY');
|
|
803
|
+
cmd('luxaura polish', 'Auto-format all .lux files');
|
|
804
|
+
cmd('luxaura shield', 'Scan for XSS, SQL injection, vault leaks');
|
|
805
|
+
br();
|
|
806
|
+
|
|
807
|
+
section('HELP');
|
|
808
|
+
cmd('luxaura help_me', 'Show this guide');
|
|
809
|
+
cmd('luxaura --version', 'Show framework version');
|
|
810
|
+
br();
|
|
811
|
+
|
|
812
|
+
console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════'));
|
|
813
|
+
console.log(chalk.hex('#a8a3ff')(' .lux Block Reference'));
|
|
814
|
+
console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
|
|
815
|
+
|
|
816
|
+
const block = (b, d) => console.log(` ${chalk.white(b.padEnd(12))} ${chalk.gray(d)}`);
|
|
817
|
+
block('receive', 'Inputs passed in from outside this scene/component');
|
|
818
|
+
block('signal', 'Reactive data — changes trigger canvas re-render');
|
|
819
|
+
block('vault', 'Server-only logic — NEVER sent to browser');
|
|
820
|
+
block('paint', 'Styling using Luxaura smart tokens (breathe/glow/curve/ink...)');
|
|
821
|
+
block('canvas', 'UI composition tree using Luxaura nodes');
|
|
822
|
+
br();
|
|
823
|
+
|
|
824
|
+
console.log(chalk.hex('#a8a3ff')(' Node Vocabulary (instead of HTML tags):\n'));
|
|
825
|
+
const node = (n, d) => console.log(` ${chalk.hex('#6c63ff')(n.padEnd(14))} ${chalk.gray(d)}`);
|
|
826
|
+
node('Frame', 'Generic block wrapper');
|
|
827
|
+
node('Scene', 'Max-width centered wrapper (like <main>)');
|
|
828
|
+
node('Strip', 'Horizontal flex row');
|
|
829
|
+
node('Stack', 'Vertical flex column');
|
|
830
|
+
node('Mosaic', 'Auto-responsive grid');
|
|
831
|
+
node('Ribbon', 'Top navigation bar');
|
|
832
|
+
node('Drawer', 'Side panel');
|
|
833
|
+
node('Anchor', 'Footer');
|
|
834
|
+
node('Crown', 'Page header');
|
|
835
|
+
node('Headline', 'Auto-selects h1–h6 by nesting depth');
|
|
836
|
+
node('Body', 'Paragraph text');
|
|
837
|
+
node('Picture', 'Image (auto-lazy loaded)');
|
|
838
|
+
node('Trigger', 'Button / clickable action');
|
|
839
|
+
node('Field', 'Text input');
|
|
840
|
+
node('Vessel', 'Form wrapper');
|
|
841
|
+
node('Toggle', 'Checkbox / switch');
|
|
842
|
+
node('Plate', 'Card surface');
|
|
843
|
+
node('Mark', 'Badge / tag');
|
|
844
|
+
node('Roster', 'List (ul)');
|
|
845
|
+
node('Entry', 'List item (li)');
|
|
846
|
+
node('Splitter', 'Horizontal divider');
|
|
847
|
+
node('Snippet', 'Inline code');
|
|
848
|
+
node('Block', 'Code block');
|
|
849
|
+
br();
|
|
850
|
+
|
|
851
|
+
console.log(chalk.hex('#a8a3ff')(' Paint Smart Tokens:\n'));
|
|
852
|
+
const token = (t, d) => console.log(` ${chalk.hex('#6c63ff')(t.padEnd(24))} ${chalk.gray(d)}`);
|
|
853
|
+
token('breathe: 4', 'padding (4 × 4px = 16px)');
|
|
854
|
+
token('push: 4', 'margin');
|
|
855
|
+
token('gap: 4', 'gap between children');
|
|
856
|
+
token('glow: soft', 'box-shadow (whisper/soft/loud/neon)');
|
|
857
|
+
token('curve: smooth', 'border-radius (gentle/smooth/round/pill)');
|
|
858
|
+
token('ink: #color', 'text color');
|
|
859
|
+
token('fill: #color', 'background color');
|
|
860
|
+
token('size: titan', 'font-size (tiny/small/base/large/xlarge/huge/titan/mega)');
|
|
861
|
+
token('weight: heavy', 'font-weight (feather/light/normal/firm/strong/heavy)');
|
|
862
|
+
token('pointer: true', 'cursor: pointer');
|
|
863
|
+
token('wide: 200', 'width in px');
|
|
864
|
+
token('tall: 100', 'height in px');
|
|
865
|
+
br();
|
|
866
|
+
|
|
867
|
+
console.log(chalk.hex('#6c63ff').bold(' ══════════════════════════════════════════════\n'));
|
|
421
868
|
});
|
|
422
869
|
|
|
423
|
-
// ─── Command: format
|
|
870
|
+
// ─── Command: polish (was: format) ───────────────────────────────────────────
|
|
424
871
|
|
|
425
872
|
program
|
|
426
|
-
.command('
|
|
427
|
-
.description('Auto-
|
|
873
|
+
.command('polish')
|
|
874
|
+
.description('Auto-format all .lux files')
|
|
428
875
|
.action(() => {
|
|
429
876
|
banner();
|
|
430
877
|
const rootDir = process.cwd();
|
|
431
|
-
const files
|
|
432
|
-
let fixed
|
|
878
|
+
const files = allLuxFiles(rootDir);
|
|
879
|
+
let fixed = 0;
|
|
433
880
|
|
|
434
|
-
files.forEach(
|
|
881
|
+
files.forEach(fp => {
|
|
435
882
|
try {
|
|
436
|
-
const
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
|
|
440
|
-
log(`Formatted: ${path.relative(rootDir, filePath)}`);
|
|
441
|
-
fixed++;
|
|
442
|
-
}
|
|
443
|
-
} catch (e) {
|
|
444
|
-
warn(`Could not format ${filePath}: ${e.message}`);
|
|
445
|
-
}
|
|
883
|
+
const src = fs.readFileSync(fp, 'utf8');
|
|
884
|
+
const fmtd = formatLux(src);
|
|
885
|
+
if (fmtd !== src) { fs.writeFileSync(fp, fmtd); log(`Polished: ${path.relative(rootDir, fp)}`); fixed++; }
|
|
886
|
+
} catch (e) { warn(`Could not polish ${fp}: ${e.message}`); }
|
|
446
887
|
});
|
|
447
888
|
|
|
448
|
-
success(`
|
|
889
|
+
success(`Polished ${fixed} file(s) out of ${files.length} scanned.`);
|
|
449
890
|
});
|
|
450
891
|
|
|
451
892
|
function formatLux(source) {
|
|
452
|
-
const
|
|
453
|
-
const lines
|
|
454
|
-
const out
|
|
455
|
-
let inBlock
|
|
893
|
+
const BLOCKS = ['receive', 'signal', 'vault', 'paint', 'canvas'];
|
|
894
|
+
const lines = source.split('\n');
|
|
895
|
+
const out = [];
|
|
896
|
+
let inBlock = false;
|
|
456
897
|
|
|
457
898
|
for (let i = 0; i < lines.length; i++) {
|
|
458
|
-
const raw
|
|
899
|
+
const raw = lines[i];
|
|
459
900
|
const trimmed = raw.trim();
|
|
460
901
|
|
|
461
902
|
if (!trimmed) { out.push(''); continue; }
|
|
462
903
|
if (trimmed.startsWith('#')) { out.push(trimmed); continue; }
|
|
463
904
|
|
|
464
|
-
if (
|
|
905
|
+
if (BLOCKS.includes(trimmed)) {
|
|
465
906
|
if (i > 0) out.push('');
|
|
466
907
|
out.push(trimmed);
|
|
467
908
|
inBlock = true;
|
|
@@ -469,9 +910,7 @@ function formatLux(source) {
|
|
|
469
910
|
}
|
|
470
911
|
|
|
471
912
|
if (inBlock) {
|
|
472
|
-
|
|
473
|
-
const indent = raw.match(/^(\s*)/)[1];
|
|
474
|
-
const spaces = indent.replace(/\t/g, ' ');
|
|
913
|
+
const spaces = raw.match(/^(\s*)/)[1].replace(/\t/g, ' ');
|
|
475
914
|
const level = Math.round(spaces.length / 4);
|
|
476
915
|
out.push(' '.repeat(Math.max(1, level)) + trimmed);
|
|
477
916
|
} else {
|
|
@@ -482,151 +921,131 @@ function formatLux(source) {
|
|
|
482
921
|
return out.join('\n').trimEnd() + '\n';
|
|
483
922
|
}
|
|
484
923
|
|
|
485
|
-
// ─── Command: secure
|
|
924
|
+
// ─── Command: shield (was: secure) ───────────────────────────────────────────
|
|
486
925
|
|
|
487
926
|
program
|
|
488
|
-
.command('
|
|
927
|
+
.command('shield')
|
|
489
928
|
.description('Scan codebase for security vulnerabilities')
|
|
490
929
|
.action(() => {
|
|
491
930
|
banner();
|
|
492
931
|
const rootDir = process.cwd();
|
|
493
|
-
const files
|
|
494
|
-
let issues
|
|
495
|
-
|
|
496
|
-
const
|
|
497
|
-
{ re: /innerHTML\s*=/g,
|
|
498
|
-
{ re: /document\.write\s*\(/g,
|
|
499
|
-
{ re: /eval\s*\(/g,
|
|
500
|
-
|
|
501
|
-
|
|
502
|
-
|
|
503
|
-
{ re: /db\.query\s*\(\s*`[^`]*\$\{/g, msg: 'Possible SQL injection: template literal in query. Use parameterized queries.' },
|
|
504
|
-
{ re: /db\.query\s*\(\s*"[^"]*"\s*\+/g, msg: 'Possible SQL injection: string concatenation in query. Use parameterized queries.' },
|
|
932
|
+
const files = allLuxFiles(rootDir);
|
|
933
|
+
let issues = 0;
|
|
934
|
+
|
|
935
|
+
const CHECKS = [
|
|
936
|
+
{ re: /innerHTML\s*=/g, msg: 'Unsafe innerHTML (XSS risk)' },
|
|
937
|
+
{ re: /document\.write\s*\(/g, msg: 'document.write() (XSS risk)' },
|
|
938
|
+
{ re: /eval\s*\(/g, msg: 'eval() usage (injection risk)' },
|
|
939
|
+
{ re: /db\.query\s*\(`[^`]*\$\{/g, msg: 'SQL injection: template literal in query — use parameterized queries' },
|
|
940
|
+
{ re: /db\.query\s*\("[^"]*"\s*\+/g, msg: 'SQL injection: string concat in query — use parameterized queries' },
|
|
941
|
+
{ re: /process\.env\.(DB_PASSWORD|SECRET|API_KEY|TOKEN)/gi, msg: 'Sensitive env var — confirm it stays in vault block' },
|
|
505
942
|
];
|
|
506
943
|
|
|
507
|
-
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
const source = fs.readFileSync(filePath, 'utf8');
|
|
513
|
-
const relPath = path.relative(rootDir, filePath);
|
|
514
|
-
|
|
515
|
-
const allPatterns = [...XSS_PATTERNS, ...SQL_PATTERNS, ...SERVER_LEAK];
|
|
516
|
-
allPatterns.forEach(({ re, msg }) => {
|
|
517
|
-
const matches = source.match(re);
|
|
518
|
-
if (matches) {
|
|
519
|
-
warn(`${relPath}: ${msg}`);
|
|
520
|
-
issues++;
|
|
521
|
-
}
|
|
944
|
+
files.forEach(fp => {
|
|
945
|
+
const src = fs.readFileSync(fp, 'utf8');
|
|
946
|
+
const relPath = path.relative(rootDir, fp);
|
|
947
|
+
CHECKS.forEach(({ re, msg }) => {
|
|
948
|
+
if (re.test(src)) { warn(`${relPath}: ${msg}`); issues++; }
|
|
522
949
|
});
|
|
523
950
|
|
|
524
|
-
//
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
if (dbOutsideServer) {
|
|
529
|
-
error(`${relPath}: db access detected outside server block — this may leak to client`);
|
|
530
|
-
issues++;
|
|
531
|
-
}
|
|
951
|
+
// Detect vault/db code outside vault block
|
|
952
|
+
if (!src.match(/^vault$/m) && /db\.(query|insert|update)/.test(src)) {
|
|
953
|
+
error(`${relPath}: db access outside vault block — may leak to browser!`);
|
|
954
|
+
issues++;
|
|
532
955
|
}
|
|
533
956
|
});
|
|
534
957
|
|
|
535
|
-
if (issues === 0) {
|
|
536
|
-
|
|
537
|
-
} else {
|
|
538
|
-
warn(`Found ${issues} potential issue(s) across ${files.length} files.`);
|
|
539
|
-
}
|
|
958
|
+
if (issues === 0) success(`Shield clear. ${files.length} file(s) scanned, 0 issues.`);
|
|
959
|
+
else warn(`Found ${issues} issue(s) in ${files.length} file(s). Review and fix before deploying.`);
|
|
540
960
|
});
|
|
541
961
|
|
|
542
|
-
// ─── Build
|
|
962
|
+
// ─── Build helpers ────────────────────────────────────────────────────────────
|
|
543
963
|
|
|
544
|
-
async function buildAll(rootDir, opts
|
|
545
|
-
|
|
546
|
-
const
|
|
547
|
-
|
|
964
|
+
async function buildAll(rootDir, opts) {
|
|
965
|
+
opts = opts || {};
|
|
966
|
+
const files = allLuxFiles(rootDir);
|
|
967
|
+
const errors = [];
|
|
968
|
+
let count = 0;
|
|
548
969
|
|
|
549
|
-
// Copy UI kit
|
|
550
|
-
const
|
|
551
|
-
const
|
|
552
|
-
|
|
553
|
-
fs.mkdirpSync(
|
|
970
|
+
// Copy UI kit to depot/public
|
|
971
|
+
const kitSrc = path.join(__dirname, '..', 'ui-kit');
|
|
972
|
+
const publicDir = path.join(rootDir, 'depot', 'public');
|
|
973
|
+
const vaultDir = path.join(rootDir, 'depot', 'vault');
|
|
974
|
+
fs.mkdirpSync(publicDir);
|
|
975
|
+
fs.mkdirpSync(vaultDir);
|
|
554
976
|
|
|
555
|
-
if (fs.existsSync(path.join(
|
|
556
|
-
fs.copySync(path.join(
|
|
557
|
-
fs.copySync(path.join(
|
|
977
|
+
if (fs.existsSync(path.join(kitSrc, 'luxaura.min.css'))) {
|
|
978
|
+
fs.copySync(path.join(kitSrc, 'luxaura.min.css'), path.join(publicDir, 'luxaura.min.css'));
|
|
979
|
+
fs.copySync(path.join(kitSrc, 'luxaura.min.js'), path.join(publicDir, 'luxaura.min.js'));
|
|
558
980
|
}
|
|
559
981
|
|
|
560
|
-
// Copy
|
|
561
|
-
const
|
|
562
|
-
if (fs.existsSync(
|
|
563
|
-
fs.copySync(assetsDir, path.join(clientDir, 'assets'));
|
|
564
|
-
}
|
|
982
|
+
// Copy media assets
|
|
983
|
+
const mediaDir = path.join(rootDir, 'media');
|
|
984
|
+
if (fs.existsSync(mediaDir)) fs.copySync(mediaDir, path.join(publicDir, 'media'));
|
|
565
985
|
|
|
566
986
|
for (const f of files) {
|
|
567
|
-
try {
|
|
568
|
-
|
|
569
|
-
count++;
|
|
570
|
-
} catch (e) {
|
|
571
|
-
errors.push(`${path.relative(rootDir, f)}: ${e.message}`);
|
|
572
|
-
}
|
|
987
|
+
try { await buildFile(f, rootDir, opts); count++; }
|
|
988
|
+
catch (e) { errors.push(`${path.relative(rootDir, f)}: ${e.message}`); }
|
|
573
989
|
}
|
|
574
990
|
|
|
575
991
|
return { count, errors };
|
|
576
992
|
}
|
|
577
993
|
|
|
578
|
-
async function buildFile(filePath, rootDir, opts
|
|
579
|
-
|
|
994
|
+
async function buildFile(filePath, rootDir, opts) {
|
|
995
|
+
opts = opts || {};
|
|
996
|
+
const source = fs.readFileSync(filePath, 'utf8');
|
|
580
997
|
const filename = path.basename(filePath);
|
|
581
|
-
const
|
|
582
|
-
const
|
|
583
|
-
const compiler = new LuxCompiler(ast);
|
|
584
|
-
const output = compiler.compile();
|
|
998
|
+
const ast = new LuxParser(source, filename).parse();
|
|
999
|
+
const output = new LuxCompiler(ast).compile();
|
|
585
1000
|
|
|
586
|
-
const
|
|
587
|
-
const
|
|
588
|
-
const name
|
|
1001
|
+
const publicDir = path.join(rootDir, 'depot', 'public');
|
|
1002
|
+
const vaultDir = path.join(rootDir, 'depot', 'vault');
|
|
1003
|
+
const name = filename.replace('.lux', '');
|
|
589
1004
|
|
|
590
|
-
fs.mkdirpSync(
|
|
591
|
-
fs.mkdirpSync(
|
|
1005
|
+
fs.mkdirpSync(publicDir);
|
|
1006
|
+
fs.mkdirpSync(vaultDir);
|
|
592
1007
|
|
|
593
|
-
// Client JS
|
|
594
|
-
fs.writeFileSync(path.join(
|
|
1008
|
+
// Client component JS
|
|
1009
|
+
fs.writeFileSync(path.join(publicDir, `${name}.component.js`), output.clientJS);
|
|
595
1010
|
|
|
596
|
-
//
|
|
597
|
-
|
|
598
|
-
|
|
1011
|
+
// Vault JS (server-only — never served publicly)
|
|
1012
|
+
const emptyVault = `/* Luxaura Vault — ${name} | no vault block */\nmodule.exports = {};`;
|
|
1013
|
+
if (output.vaultJS && output.vaultJS !== emptyVault) {
|
|
1014
|
+
fs.writeFileSync(path.join(vaultDir, `${name}.js`), output.vaultJS);
|
|
599
1015
|
}
|
|
600
1016
|
|
|
601
|
-
//
|
|
602
|
-
if (output.
|
|
603
|
-
const cssPath = path.join(
|
|
604
|
-
const
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
}
|
|
1017
|
+
// Paint CSS — append per-component styles to global paint.css
|
|
1018
|
+
if (output.paintCSS) {
|
|
1019
|
+
const cssPath = path.join(publicDir, 'paint.css');
|
|
1020
|
+
const exists = fs.existsSync(cssPath) ? fs.readFileSync(cssPath, 'utf8') : '';
|
|
1021
|
+
const marker = `/* Luxaura Paint — ${name} */`;
|
|
1022
|
+
if (!exists.includes(marker)) fs.appendFileSync(cssPath, '\n' + output.paintCSS);
|
|
608
1023
|
}
|
|
609
1024
|
|
|
610
|
-
// HTML shell
|
|
611
|
-
|
|
612
|
-
|
|
1025
|
+
// HTML shell — only for scenes (route entry points)
|
|
1026
|
+
const isScene = filePath.includes(`${path.sep}scenes${path.sep}`) ||
|
|
1027
|
+
filePath.replace(/\\/g, '/').includes('/scenes/');
|
|
1028
|
+
|
|
1029
|
+
if (isScene) {
|
|
1030
|
+
const htmlName = name === 'index' ? 'index.html' : `${name}.html`;
|
|
1031
|
+
const htmlPath = path.join(publicDir, htmlName);
|
|
613
1032
|
fs.writeFileSync(htmlPath, output.html);
|
|
614
1033
|
|
|
615
|
-
// Bundle app.js
|
|
616
|
-
const appJsPath = path.join(
|
|
617
|
-
const
|
|
1034
|
+
// Bundle app.js from all component JS files
|
|
1035
|
+
const appJsPath = path.join(publicDir, 'app.js');
|
|
1036
|
+
const bundle = fs.readdirSync(publicDir)
|
|
618
1037
|
.filter(f => f.endsWith('.component.js'))
|
|
619
|
-
.map(f =>
|
|
1038
|
+
.map(f => `/* ── ${f} ── */\n` + fs.readFileSync(path.join(publicDir, f), 'utf8'))
|
|
620
1039
|
.join('\n\n');
|
|
621
|
-
fs.writeFileSync(appJsPath,
|
|
1040
|
+
fs.writeFileSync(appJsPath, bundle);
|
|
622
1041
|
}
|
|
623
1042
|
}
|
|
624
1043
|
|
|
625
|
-
// ───
|
|
1044
|
+
// ─── Boot ─────────────────────────────────────────────────────────────────────
|
|
626
1045
|
|
|
627
1046
|
program
|
|
628
1047
|
.name('luxaura')
|
|
629
|
-
.version(VERSION)
|
|
630
|
-
.description('Luxaura Framework
|
|
1048
|
+
.version(VERSION, '-v, --version', 'Show Luxaura version')
|
|
1049
|
+
.description('Luxaura — The Intent-Driven Full-Stack Web Framework');
|
|
631
1050
|
|
|
632
1051
|
program.parse(process.argv);
|