ether-code 0.1.9 → 0.2.3
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/LICENSE +1 -1
- package/README.md +27 -89
- package/cli/compiler.js +11 -25
- package/cli/ether.js +1095 -0
- package/cli/watcher.js +106 -0
- package/ether-compiler.js +1 -1
- package/ether-parser.js +9 -3
- package/generators/html-generator.js +1 -0
- package/i18n/i18n-css.json +743 -0
- package/i18n/i18n-graphql.json +1531 -0
- package/i18n/i18n-html.json +572 -0
- package/i18n/i18n-js.json +2790 -0
- package/i18n/i18n-node.json +2442 -0
- package/i18n/i18n-php.json +4306 -0
- package/i18n/i18n-python.json +3080 -0
- package/i18n/i18n-react.json +1784 -0
- package/i18n/i18n-ruby.json +1858 -0
- package/i18n/i18n-sql.json +3466 -0
- package/i18n/i18n-ts.json +442 -0
- package/lexer/ether-lexer.js +869 -0
- package/lexer/tokens.js +292 -0
- package/package.json +1 -1
package/cli/ether.js
ADDED
|
@@ -0,0 +1,1095 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
const fs = require('fs')
|
|
4
|
+
const path = require('path')
|
|
5
|
+
const http = require('http')
|
|
6
|
+
const { EtherCompiler } = require('./compiler')
|
|
7
|
+
const { Watcher } = require('./watcher')
|
|
8
|
+
|
|
9
|
+
const VERSION = '0.2.3'
|
|
10
|
+
|
|
11
|
+
const COLORS = {
|
|
12
|
+
reset: '\x1b[0m',
|
|
13
|
+
bright: '\x1b[1m',
|
|
14
|
+
dim: '\x1b[2m',
|
|
15
|
+
red: '\x1b[31m',
|
|
16
|
+
green: '\x1b[32m',
|
|
17
|
+
yellow: '\x1b[33m',
|
|
18
|
+
blue: '\x1b[34m',
|
|
19
|
+
magenta: '\x1b[35m',
|
|
20
|
+
cyan: '\x1b[36m'
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function log(msg, color = '') {
|
|
24
|
+
console.log(`${color}${msg}${COLORS.reset}`)
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
function logSuccess(msg) {
|
|
28
|
+
log(`✓ ${msg}`, COLORS.green)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
function logError(msg) {
|
|
32
|
+
log(`✗ ${msg}`, COLORS.red)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function logInfo(msg) {
|
|
36
|
+
log(`ℹ ${msg}`, COLORS.cyan)
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
function logWarning(msg) {
|
|
40
|
+
log(`⚠ ${msg}`, COLORS.yellow)
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function showBanner() {
|
|
44
|
+
console.log(`
|
|
45
|
+
${COLORS.cyan}${COLORS.bright}
|
|
46
|
+
███████╗████████╗██╗ ██╗███████╗██████╗
|
|
47
|
+
██╔════╝╚══██╔══╝██║ ██║██╔════╝██╔══██╗
|
|
48
|
+
█████╗ ██║ ███████║█████╗ ██████╔╝
|
|
49
|
+
██╔══╝ ██║ ██╔══██║██╔══╝ ██╔══██╗
|
|
50
|
+
███████╗ ██║ ██║ ██║███████╗██║ ██║
|
|
51
|
+
╚══════╝ ╚═╝ ╚═╝ ╚═╝╚══════╝╚═╝ ╚═╝
|
|
52
|
+
${COLORS.reset}
|
|
53
|
+
${COLORS.dim}Le langage intentionnel - v${VERSION}${COLORS.reset}
|
|
54
|
+
`)
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function showHelp() {
|
|
58
|
+
showBanner()
|
|
59
|
+
console.log(`
|
|
60
|
+
${COLORS.bright}UTILISATION${COLORS.reset}
|
|
61
|
+
ether <commande> [options]
|
|
62
|
+
|
|
63
|
+
${COLORS.bright}COMMANDES${COLORS.reset}
|
|
64
|
+
${COLORS.cyan}init${COLORS.reset} Initialiser un nouveau projet Ether
|
|
65
|
+
${COLORS.cyan}build${COLORS.reset} Compiler les fichiers .eth
|
|
66
|
+
${COLORS.cyan}dev${COLORS.reset} Mode développement (watch + serveur + live-reload)
|
|
67
|
+
${COLORS.cyan}check${COLORS.reset} Vérifier la syntaxe sans compiler
|
|
68
|
+
${COLORS.cyan}help${COLORS.reset} Afficher cette aide
|
|
69
|
+
${COLORS.cyan}version${COLORS.reset} Afficher la version
|
|
70
|
+
|
|
71
|
+
${COLORS.bright}OPTIONS${COLORS.reset}
|
|
72
|
+
-c, --config Chemin vers le fichier de configuration
|
|
73
|
+
-o, --output Dossier de sortie
|
|
74
|
+
-p, --port Port du serveur (défaut: 3000)
|
|
75
|
+
-w, --watch Surveiller les changements (alias de dev)
|
|
76
|
+
-v, --verbose Mode verbeux
|
|
77
|
+
-q, --quiet Mode silencieux
|
|
78
|
+
--no-color Désactiver les couleurs
|
|
79
|
+
--no-server Désactiver le serveur en mode dev
|
|
80
|
+
|
|
81
|
+
${COLORS.bright}EXEMPLES${COLORS.reset}
|
|
82
|
+
${COLORS.dim}# Initialiser un projet${COLORS.reset}
|
|
83
|
+
ether init
|
|
84
|
+
|
|
85
|
+
${COLORS.dim}# Compiler le projet${COLORS.reset}
|
|
86
|
+
ether build
|
|
87
|
+
|
|
88
|
+
${COLORS.dim}# Mode développement avec serveur${COLORS.reset}
|
|
89
|
+
ether dev
|
|
90
|
+
|
|
91
|
+
${COLORS.dim}# Mode développement sur port 8080${COLORS.reset}
|
|
92
|
+
ether dev -p 8080
|
|
93
|
+
|
|
94
|
+
${COLORS.bright}DOCUMENTATION${COLORS.reset}
|
|
95
|
+
https://ether-code.com/docs
|
|
96
|
+
`)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
function showVersion() {
|
|
100
|
+
console.log(`Ether v${VERSION}`)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
function loadConfig(configPath) {
|
|
104
|
+
const defaultConfig = {
|
|
105
|
+
src: './src',
|
|
106
|
+
out: './dist',
|
|
107
|
+
targets: {},
|
|
108
|
+
i18n: 'fr',
|
|
109
|
+
port: 3000,
|
|
110
|
+
watch: {
|
|
111
|
+
ignored: ['node_modules', '.git', 'dist']
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
const configFile = configPath || path.join(process.cwd(), 'ether.config.js')
|
|
116
|
+
|
|
117
|
+
if (fs.existsSync(configFile)) {
|
|
118
|
+
try {
|
|
119
|
+
const userConfig = require(configFile)
|
|
120
|
+
return { ...defaultConfig, ...userConfig }
|
|
121
|
+
} catch (err) {
|
|
122
|
+
logError(`Erreur de configuration: ${err.message}`)
|
|
123
|
+
return defaultConfig
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
|
|
127
|
+
const jsonConfig = path.join(process.cwd(), 'ether.config.json')
|
|
128
|
+
if (fs.existsSync(jsonConfig)) {
|
|
129
|
+
try {
|
|
130
|
+
const content = fs.readFileSync(jsonConfig, 'utf-8')
|
|
131
|
+
const userConfig = JSON.parse(content)
|
|
132
|
+
return { ...defaultConfig, ...userConfig }
|
|
133
|
+
} catch (err) {
|
|
134
|
+
logError(`Erreur de configuration: ${err.message}`)
|
|
135
|
+
return defaultConfig
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
|
|
139
|
+
return defaultConfig
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
const MIME_TYPES = {
|
|
143
|
+
'.html': 'text/html',
|
|
144
|
+
'.css': 'text/css',
|
|
145
|
+
'.js': 'application/javascript',
|
|
146
|
+
'.json': 'application/json',
|
|
147
|
+
'.png': 'image/png',
|
|
148
|
+
'.jpg': 'image/jpeg',
|
|
149
|
+
'.jpeg': 'image/jpeg',
|
|
150
|
+
'.gif': 'image/gif',
|
|
151
|
+
'.svg': 'image/svg+xml',
|
|
152
|
+
'.ico': 'image/x-icon',
|
|
153
|
+
'.woff': 'font/woff',
|
|
154
|
+
'.woff2': 'font/woff2',
|
|
155
|
+
'.ttf': 'font/ttf',
|
|
156
|
+
'.eot': 'application/vnd.ms-fontobject'
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
const LIVE_RELOAD_SCRIPT = `
|
|
160
|
+
<script>
|
|
161
|
+
(function() {
|
|
162
|
+
var source = new EventSource('/__ether_live_reload');
|
|
163
|
+
source.onmessage = function(e) {
|
|
164
|
+
if (e.data === 'reload') {
|
|
165
|
+
window.location.reload();
|
|
166
|
+
}
|
|
167
|
+
};
|
|
168
|
+
source.onerror = function() {
|
|
169
|
+
console.log('[Ether] Connexion au serveur perdue, tentative de reconnexion...');
|
|
170
|
+
};
|
|
171
|
+
})();
|
|
172
|
+
</script>
|
|
173
|
+
`
|
|
174
|
+
|
|
175
|
+
function createDevServer(outDir, port) {
|
|
176
|
+
const clients = []
|
|
177
|
+
|
|
178
|
+
const server = http.createServer((req, res) => {
|
|
179
|
+
if (req.url === '/__ether_live_reload') {
|
|
180
|
+
res.writeHead(200, {
|
|
181
|
+
'Content-Type': 'text/event-stream',
|
|
182
|
+
'Cache-Control': 'no-cache',
|
|
183
|
+
'Connection': 'keep-alive',
|
|
184
|
+
'Access-Control-Allow-Origin': '*'
|
|
185
|
+
})
|
|
186
|
+
res.write('data: connected\n\n')
|
|
187
|
+
clients.push(res)
|
|
188
|
+
req.on('close', () => {
|
|
189
|
+
const index = clients.indexOf(res)
|
|
190
|
+
if (index !== -1) clients.splice(index, 1)
|
|
191
|
+
})
|
|
192
|
+
return
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
let filePath = path.join(outDir, req.url === '/' ? 'index.html' : req.url)
|
|
196
|
+
|
|
197
|
+
if (!filePath.includes('.') && !filePath.endsWith('/')) {
|
|
198
|
+
const htmlPath = filePath + '.html'
|
|
199
|
+
if (fs.existsSync(htmlPath)) {
|
|
200
|
+
filePath = htmlPath
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
if (fs.existsSync(filePath) && fs.statSync(filePath).isDirectory()) {
|
|
205
|
+
filePath = path.join(filePath, 'index.html')
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
if (!fs.existsSync(filePath)) {
|
|
209
|
+
res.writeHead(404, { 'Content-Type': 'text/html' })
|
|
210
|
+
res.end(`
|
|
211
|
+
<!DOCTYPE html>
|
|
212
|
+
<html>
|
|
213
|
+
<head><title>404 - Non trouvé</title></head>
|
|
214
|
+
<body style="font-family: system-ui; padding: 40px; text-align: center;">
|
|
215
|
+
<h1>404</h1>
|
|
216
|
+
<p>Fichier non trouvé: ${req.url}</p>
|
|
217
|
+
<p style="color: #666;">Serveur Ether Dev</p>
|
|
218
|
+
</body>
|
|
219
|
+
</html>
|
|
220
|
+
`)
|
|
221
|
+
return
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
const ext = path.extname(filePath).toLowerCase()
|
|
225
|
+
const mimeType = MIME_TYPES[ext] || 'application/octet-stream'
|
|
226
|
+
|
|
227
|
+
fs.readFile(filePath, (err, content) => {
|
|
228
|
+
if (err) {
|
|
229
|
+
res.writeHead(500)
|
|
230
|
+
res.end(`Erreur serveur: ${err.message}`)
|
|
231
|
+
return
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
if (ext === '.html') {
|
|
235
|
+
let html = content.toString()
|
|
236
|
+
if (html.includes('</body>')) {
|
|
237
|
+
html = html.replace('</body>', `${LIVE_RELOAD_SCRIPT}</body>`)
|
|
238
|
+
} else if (html.includes('</html>')) {
|
|
239
|
+
html = html.replace('</html>', `${LIVE_RELOAD_SCRIPT}</html>`)
|
|
240
|
+
} else {
|
|
241
|
+
html += LIVE_RELOAD_SCRIPT
|
|
242
|
+
}
|
|
243
|
+
content = html
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
res.writeHead(200, { 'Content-Type': mimeType })
|
|
247
|
+
res.end(content)
|
|
248
|
+
})
|
|
249
|
+
})
|
|
250
|
+
|
|
251
|
+
server.listen(port, () => {
|
|
252
|
+
logSuccess(`Serveur démarré sur ${COLORS.cyan}http://localhost:${port}${COLORS.reset}`)
|
|
253
|
+
})
|
|
254
|
+
|
|
255
|
+
server.on('error', (err) => {
|
|
256
|
+
if (err.code === 'EADDRINUSE') {
|
|
257
|
+
logError(`Le port ${port} est déjà utilisé. Essayez: ether dev -p ${port + 1}`)
|
|
258
|
+
} else {
|
|
259
|
+
logError(`Erreur serveur: ${err.message}`)
|
|
260
|
+
}
|
|
261
|
+
process.exit(1)
|
|
262
|
+
})
|
|
263
|
+
|
|
264
|
+
return {
|
|
265
|
+
server,
|
|
266
|
+
reload: () => {
|
|
267
|
+
clients.forEach(client => {
|
|
268
|
+
client.write('data: reload\n\n')
|
|
269
|
+
})
|
|
270
|
+
},
|
|
271
|
+
close: () => {
|
|
272
|
+
clients.forEach(client => client.end())
|
|
273
|
+
server.close()
|
|
274
|
+
}
|
|
275
|
+
}
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
async function cmdInit() {
|
|
279
|
+
showBanner()
|
|
280
|
+
logInfo('Initialisation du projet Ether...')
|
|
281
|
+
|
|
282
|
+
const cwd = process.cwd()
|
|
283
|
+
|
|
284
|
+
const srcDir = path.join(cwd, 'src')
|
|
285
|
+
if (!fs.existsSync(srcDir)) {
|
|
286
|
+
fs.mkdirSync(srcDir, { recursive: true })
|
|
287
|
+
logSuccess('Dossier src/ créé')
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const distDir = path.join(cwd, 'dist')
|
|
291
|
+
if (!fs.existsSync(distDir)) {
|
|
292
|
+
fs.mkdirSync(distDir, { recursive: true })
|
|
293
|
+
logSuccess('Dossier dist/ créé')
|
|
294
|
+
}
|
|
295
|
+
|
|
296
|
+
const publicDir = path.join(cwd, 'public')
|
|
297
|
+
if (!fs.existsSync(publicDir)) {
|
|
298
|
+
fs.mkdirSync(publicDir, { recursive: true })
|
|
299
|
+
logSuccess('Dossier public/ créé')
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
const imagesDir = path.join(publicDir, 'images')
|
|
303
|
+
if (!fs.existsSync(imagesDir)) {
|
|
304
|
+
fs.mkdirSync(imagesDir, { recursive: true })
|
|
305
|
+
logSuccess('Dossier public/images/ créé')
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
const videosDir = path.join(publicDir, 'videos')
|
|
309
|
+
if (!fs.existsSync(videosDir)) {
|
|
310
|
+
fs.mkdirSync(videosDir, { recursive: true })
|
|
311
|
+
logSuccess('Dossier public/videos/ créé')
|
|
312
|
+
}
|
|
313
|
+
|
|
314
|
+
const projectName = path.basename(cwd)
|
|
315
|
+
const packageJsonPath = path.join(cwd, 'package.json')
|
|
316
|
+
if (!fs.existsSync(packageJsonPath)) {
|
|
317
|
+
const packageJson = {
|
|
318
|
+
name: projectName.toLowerCase().replace(/[^a-z0-9-]/g, '-'),
|
|
319
|
+
version: '1.0.0',
|
|
320
|
+
description: 'Projet Ether',
|
|
321
|
+
main: 'dist/index.html',
|
|
322
|
+
scripts: {
|
|
323
|
+
dev: 'ether dev',
|
|
324
|
+
build: 'ether build'
|
|
325
|
+
},
|
|
326
|
+
keywords: ['ether'],
|
|
327
|
+
author: '',
|
|
328
|
+
license: 'MIT'
|
|
329
|
+
}
|
|
330
|
+
fs.writeFileSync(packageJsonPath, JSON.stringify(packageJson, null, 2))
|
|
331
|
+
logSuccess('Fichier package.json créé')
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
const configContent = `module.exports = {
|
|
335
|
+
src: './src',
|
|
336
|
+
out: './dist',
|
|
337
|
+
|
|
338
|
+
i18n: 'fr',
|
|
339
|
+
port: 3000,
|
|
340
|
+
|
|
341
|
+
targets: {
|
|
342
|
+
css: {
|
|
343
|
+
extension: '.css',
|
|
344
|
+
minify: false
|
|
345
|
+
},
|
|
346
|
+
html: {
|
|
347
|
+
extension: '.html',
|
|
348
|
+
minify: false
|
|
349
|
+
},
|
|
350
|
+
js: {
|
|
351
|
+
extension: '.js',
|
|
352
|
+
minify: false
|
|
353
|
+
}
|
|
354
|
+
},
|
|
355
|
+
|
|
356
|
+
watch: {
|
|
357
|
+
ignored: ['node_modules', '.git', 'dist']
|
|
358
|
+
}
|
|
359
|
+
}
|
|
360
|
+
`
|
|
361
|
+
|
|
362
|
+
const configPath = path.join(cwd, 'ether.config.js')
|
|
363
|
+
if (!fs.existsSync(configPath)) {
|
|
364
|
+
fs.writeFileSync(configPath, configContent)
|
|
365
|
+
logSuccess('Fichier ether.config.js créé')
|
|
366
|
+
} else {
|
|
367
|
+
logWarning('ether.config.js existe déjà')
|
|
368
|
+
}
|
|
369
|
+
|
|
370
|
+
const exampleHtml = `// cible: html
|
|
371
|
+
|
|
372
|
+
document
|
|
373
|
+
tete
|
|
374
|
+
meta charset: "UTF-8"
|
|
375
|
+
meta name: "viewport", content: "width=device-width, initial-scale=1.0"
|
|
376
|
+
titre "Ether - Le langage intentionnel"
|
|
377
|
+
lien rel: "stylesheet", href: "styles.css"
|
|
378
|
+
|
|
379
|
+
corps
|
|
380
|
+
entete classe: "hero"
|
|
381
|
+
nav classe: "navbar"
|
|
382
|
+
div classe: "logo"
|
|
383
|
+
span classe: "logo-icon"
|
|
384
|
+
"◈"
|
|
385
|
+
span "Ether"
|
|
386
|
+
liste-non-ordonnee classe: "nav-links"
|
|
387
|
+
element-liste
|
|
388
|
+
lien href: "#features"
|
|
389
|
+
"Fonctionnalités"
|
|
390
|
+
element-liste
|
|
391
|
+
lien href: "#examples"
|
|
392
|
+
"Exemples"
|
|
393
|
+
element-liste
|
|
394
|
+
lien href: "#docs"
|
|
395
|
+
"Documentation"
|
|
396
|
+
|
|
397
|
+
div classe: "hero-content"
|
|
398
|
+
titre1 classe: "hero-title"
|
|
399
|
+
"Écrivez du code"
|
|
400
|
+
saut-ligne
|
|
401
|
+
span classe: "gradient-text"
|
|
402
|
+
"comme vous pensez"
|
|
403
|
+
paragraphe classe: "hero-subtitle"
|
|
404
|
+
"Ether est un langage intentionnel qui compile vers HTML, CSS, JavaScript et plus encore. Naturel, intuitif, puissant."
|
|
405
|
+
div classe: "hero-buttons"
|
|
406
|
+
lien href: "#start", classe: "btn btn-primary"
|
|
407
|
+
"Commencer"
|
|
408
|
+
lien href: "#learn", classe: "btn btn-secondary"
|
|
409
|
+
"En savoir plus"
|
|
410
|
+
|
|
411
|
+
principal
|
|
412
|
+
section id: "features", classe: "features"
|
|
413
|
+
titre2 classe: "section-title"
|
|
414
|
+
"Pourquoi Ether ?"
|
|
415
|
+
div classe: "features-grid"
|
|
416
|
+
article classe: "feature-card"
|
|
417
|
+
div classe: "feature-icon"
|
|
418
|
+
"🎯"
|
|
419
|
+
titre3 "Intuitif"
|
|
420
|
+
paragraphe "Écrivez du code en langage naturel. Plus besoin de mémoriser des syntaxes complexes."
|
|
421
|
+
article classe: "feature-card"
|
|
422
|
+
div classe: "feature-icon"
|
|
423
|
+
"⚡"
|
|
424
|
+
titre3 "Rapide"
|
|
425
|
+
paragraphe "Compilation instantanée avec rechargement automatique en mode développement."
|
|
426
|
+
article classe: "feature-card"
|
|
427
|
+
div classe: "feature-icon"
|
|
428
|
+
"🌍"
|
|
429
|
+
titre3 "Multilingue"
|
|
430
|
+
paragraphe "Codez en français, anglais, espagnol et plus. Votre langue, votre code."
|
|
431
|
+
article classe: "feature-card"
|
|
432
|
+
div classe: "feature-icon"
|
|
433
|
+
"🔧"
|
|
434
|
+
titre3 "Flexible"
|
|
435
|
+
paragraphe "Compile vers HTML, CSS, JS, PHP, Python, SQL et bien d'autres langages."
|
|
436
|
+
|
|
437
|
+
section id: "examples", classe: "examples"
|
|
438
|
+
titre2 classe: "section-title"
|
|
439
|
+
"Simple et élégant"
|
|
440
|
+
div classe: "code-comparison"
|
|
441
|
+
div classe: "code-block"
|
|
442
|
+
div classe: "code-header"
|
|
443
|
+
"Ether"
|
|
444
|
+
pre
|
|
445
|
+
code
|
|
446
|
+
"document\\n corps\\n titre1 \\"Bonjour\\"\\n paragraphe \\"Le monde\\""
|
|
447
|
+
div classe: "code-arrow"
|
|
448
|
+
"→"
|
|
449
|
+
div classe: "code-block"
|
|
450
|
+
div classe: "code-header"
|
|
451
|
+
"HTML"
|
|
452
|
+
pre
|
|
453
|
+
code
|
|
454
|
+
"<!DOCTYPE html>\\n<html>\\n <body>\\n <h1>Bonjour</h1>\\n <p>Le monde</p>\\n </body>\\n</html>"
|
|
455
|
+
|
|
456
|
+
pied classe: "footer"
|
|
457
|
+
paragraphe
|
|
458
|
+
"Créé avec"
|
|
459
|
+
span classe: "heart"
|
|
460
|
+
" ♥ "
|
|
461
|
+
"par la communauté Ether"
|
|
462
|
+
paragraphe classe: "copyright"
|
|
463
|
+
"© 2025 Ether. MIT License."
|
|
464
|
+
|
|
465
|
+
script src: "app.js"
|
|
466
|
+
`
|
|
467
|
+
|
|
468
|
+
const exampleCss = `// cible: css
|
|
469
|
+
|
|
470
|
+
racine
|
|
471
|
+
couleur-primaire: "#6366f1"
|
|
472
|
+
couleur-secondaire: "#8b5cf6"
|
|
473
|
+
couleur-accent: "#06b6d4"
|
|
474
|
+
couleur-fond: "#0f0f23"
|
|
475
|
+
couleur-surface: "#1a1a2e"
|
|
476
|
+
couleur-texte: "#e2e8f0"
|
|
477
|
+
couleur-texte-pale: "#94a3b8"
|
|
478
|
+
|
|
479
|
+
*
|
|
480
|
+
marge: 0
|
|
481
|
+
remplissage: 0
|
|
482
|
+
modele-boite: border-box
|
|
483
|
+
|
|
484
|
+
corps
|
|
485
|
+
famille-police: "system-ui", "-apple-system", "sans-serif"
|
|
486
|
+
couleur-fond: var(--couleur-fond)
|
|
487
|
+
couleur: var(--couleur-texte)
|
|
488
|
+
hauteur-ligne: 1.6
|
|
489
|
+
|
|
490
|
+
.hero
|
|
491
|
+
min-hauteur: 100vh
|
|
492
|
+
fond: "linear-gradient(135deg, var(--couleur-fond) 0%, var(--couleur-surface) 100%)"
|
|
493
|
+
position: relative
|
|
494
|
+
debordement: cache
|
|
495
|
+
|
|
496
|
+
.navbar
|
|
497
|
+
affichage: flex
|
|
498
|
+
justifier-contenu: space-between
|
|
499
|
+
aligner-elements: center
|
|
500
|
+
remplissage: "1.5rem 5%"
|
|
501
|
+
position: fixed
|
|
502
|
+
largeur: 100%
|
|
503
|
+
haut: 0
|
|
504
|
+
z-index: 100
|
|
505
|
+
fond: "rgba(15, 15, 35, 0.9)"
|
|
506
|
+
flou-fond: "10px"
|
|
507
|
+
|
|
508
|
+
.logo
|
|
509
|
+
affichage: flex
|
|
510
|
+
aligner-elements: center
|
|
511
|
+
espace: "0.5rem"
|
|
512
|
+
taille-police: "1.5rem"
|
|
513
|
+
poids-police: 700
|
|
514
|
+
|
|
515
|
+
.logo-icon
|
|
516
|
+
taille-police: "2rem"
|
|
517
|
+
couleur: var(--couleur-primaire)
|
|
518
|
+
|
|
519
|
+
.nav-links
|
|
520
|
+
affichage: flex
|
|
521
|
+
liste-style: none
|
|
522
|
+
espace: "2rem"
|
|
523
|
+
|
|
524
|
+
.nav-links lien
|
|
525
|
+
couleur: var(--couleur-texte-pale)
|
|
526
|
+
decoration-texte: none
|
|
527
|
+
transition: "couleur 0.3s"
|
|
528
|
+
au survol
|
|
529
|
+
couleur: var(--couleur-primaire)
|
|
530
|
+
|
|
531
|
+
.hero-content
|
|
532
|
+
texte-aligne: center
|
|
533
|
+
remplissage: "12rem 5% 5rem"
|
|
534
|
+
max-largeur: "800px"
|
|
535
|
+
marge: "0 auto"
|
|
536
|
+
|
|
537
|
+
.hero-title
|
|
538
|
+
taille-police: "3.5rem"
|
|
539
|
+
poids-police: 800
|
|
540
|
+
marge-bas: "1.5rem"
|
|
541
|
+
hauteur-ligne: 1.2
|
|
542
|
+
|
|
543
|
+
.gradient-text
|
|
544
|
+
fond: "linear-gradient(90deg, var(--couleur-primaire), var(--couleur-accent))"
|
|
545
|
+
decoupe-fond: text
|
|
546
|
+
couleur: transparent
|
|
547
|
+
|
|
548
|
+
.hero-subtitle
|
|
549
|
+
taille-police: "1.25rem"
|
|
550
|
+
couleur: var(--couleur-texte-pale)
|
|
551
|
+
marge-bas: "2.5rem"
|
|
552
|
+
max-largeur: "600px"
|
|
553
|
+
marge-gauche: auto
|
|
554
|
+
marge-droite: auto
|
|
555
|
+
|
|
556
|
+
.hero-buttons
|
|
557
|
+
affichage: flex
|
|
558
|
+
espace: "1rem"
|
|
559
|
+
justifier-contenu: center
|
|
560
|
+
|
|
561
|
+
.btn
|
|
562
|
+
remplissage: "0.875rem 2rem"
|
|
563
|
+
rayon-bordure: "0.5rem"
|
|
564
|
+
taille-police: "1rem"
|
|
565
|
+
poids-police: 600
|
|
566
|
+
decoration-texte: none
|
|
567
|
+
transition: "transform 0.2s, box-shadow 0.2s"
|
|
568
|
+
au survol
|
|
569
|
+
transformation: "translateY(-2px)"
|
|
570
|
+
|
|
571
|
+
.btn-primary
|
|
572
|
+
fond: "linear-gradient(90deg, var(--couleur-primaire), var(--couleur-secondaire))"
|
|
573
|
+
couleur: white
|
|
574
|
+
au survol
|
|
575
|
+
ombre-boite: "0 10px 30px rgba(99, 102, 241, 0.4)"
|
|
576
|
+
|
|
577
|
+
.btn-secondary
|
|
578
|
+
fond: transparent
|
|
579
|
+
couleur: var(--couleur-texte)
|
|
580
|
+
bordure: "2px solid var(--couleur-primaire)"
|
|
581
|
+
au survol
|
|
582
|
+
fond: "rgba(99, 102, 241, 0.1)"
|
|
583
|
+
|
|
584
|
+
.features
|
|
585
|
+
remplissage: "6rem 5%"
|
|
586
|
+
|
|
587
|
+
.section-title
|
|
588
|
+
texte-aligne: center
|
|
589
|
+
taille-police: "2.5rem"
|
|
590
|
+
marge-bas: "3rem"
|
|
591
|
+
|
|
592
|
+
.features-grid
|
|
593
|
+
affichage: grid
|
|
594
|
+
colonnes-grille: "repeat(auto-fit, minmax(250px, 1fr))"
|
|
595
|
+
espace: "2rem"
|
|
596
|
+
max-largeur: "1200px"
|
|
597
|
+
marge: "0 auto"
|
|
598
|
+
|
|
599
|
+
.feature-card
|
|
600
|
+
fond: var(--couleur-surface)
|
|
601
|
+
remplissage: "2rem"
|
|
602
|
+
rayon-bordure: "1rem"
|
|
603
|
+
texte-aligne: center
|
|
604
|
+
transition: "transform 0.3s"
|
|
605
|
+
au survol
|
|
606
|
+
transformation: "translateY(-5px)"
|
|
607
|
+
|
|
608
|
+
.feature-icon
|
|
609
|
+
taille-police: "3rem"
|
|
610
|
+
marge-bas: "1rem"
|
|
611
|
+
|
|
612
|
+
.feature-card titre3
|
|
613
|
+
marge-bas: "0.75rem"
|
|
614
|
+
taille-police: "1.25rem"
|
|
615
|
+
|
|
616
|
+
.feature-card paragraphe
|
|
617
|
+
couleur: var(--couleur-texte-pale)
|
|
618
|
+
|
|
619
|
+
.examples
|
|
620
|
+
remplissage: "6rem 5%"
|
|
621
|
+
fond: var(--couleur-surface)
|
|
622
|
+
|
|
623
|
+
.code-comparison
|
|
624
|
+
affichage: flex
|
|
625
|
+
aligner-elements: center
|
|
626
|
+
justifier-contenu: center
|
|
627
|
+
espace: "2rem"
|
|
628
|
+
flex-wrap: wrap
|
|
629
|
+
max-largeur: "1000px"
|
|
630
|
+
marge: "0 auto"
|
|
631
|
+
|
|
632
|
+
.code-block
|
|
633
|
+
fond: var(--couleur-fond)
|
|
634
|
+
rayon-bordure: "0.75rem"
|
|
635
|
+
debordement: cache
|
|
636
|
+
min-largeur: "300px"
|
|
637
|
+
|
|
638
|
+
.code-header
|
|
639
|
+
fond: "rgba(99, 102, 241, 0.2)"
|
|
640
|
+
remplissage: "0.75rem 1rem"
|
|
641
|
+
poids-police: 600
|
|
642
|
+
couleur: var(--couleur-primaire)
|
|
643
|
+
|
|
644
|
+
.code-block pre
|
|
645
|
+
remplissage: "1.5rem"
|
|
646
|
+
marge: 0
|
|
647
|
+
|
|
648
|
+
.code-block code
|
|
649
|
+
famille-police: "Fira Code", "Monaco", monospace
|
|
650
|
+
taille-police: "0.9rem"
|
|
651
|
+
blanc: pre
|
|
652
|
+
|
|
653
|
+
.code-arrow
|
|
654
|
+
taille-police: "2rem"
|
|
655
|
+
couleur: var(--couleur-primaire)
|
|
656
|
+
|
|
657
|
+
.footer
|
|
658
|
+
texte-aligne: center
|
|
659
|
+
remplissage: "3rem 5%"
|
|
660
|
+
fond: var(--couleur-fond)
|
|
661
|
+
|
|
662
|
+
.heart
|
|
663
|
+
couleur: "#ef4444"
|
|
664
|
+
|
|
665
|
+
.copyright
|
|
666
|
+
marge-haut: "0.5rem"
|
|
667
|
+
couleur: var(--couleur-texte-pale)
|
|
668
|
+
taille-police: "0.875rem"
|
|
669
|
+
|
|
670
|
+
@media (max-largeur: 768px)
|
|
671
|
+
.hero-title
|
|
672
|
+
taille-police: "2.5rem"
|
|
673
|
+
.nav-links
|
|
674
|
+
affichage: none
|
|
675
|
+
.code-arrow
|
|
676
|
+
transformation: "rotate(90deg)"
|
|
677
|
+
`
|
|
678
|
+
|
|
679
|
+
const exampleJs = `// cible: js
|
|
680
|
+
|
|
681
|
+
constante annee = nouveau Date().getFullYear()
|
|
682
|
+
|
|
683
|
+
fonction initialiser()
|
|
684
|
+
constante copyright = document.querySelector(".copyright")
|
|
685
|
+
si copyright
|
|
686
|
+
copyright.textContent = "© " + annee + " Ether. MIT License."
|
|
687
|
+
|
|
688
|
+
constante liens = document.querySelectorAll("a[href^='#']")
|
|
689
|
+
pour chaque lien dans liens
|
|
690
|
+
lien.addEventListener "click", fonction(e)
|
|
691
|
+
e.preventDefault()
|
|
692
|
+
constante cible = document.querySelector(this.getAttribute("href"))
|
|
693
|
+
si cible
|
|
694
|
+
cible.scrollIntoView({ behavior: "smooth" })
|
|
695
|
+
|
|
696
|
+
document.addEventListener "DOMContentLoaded", initialiser
|
|
697
|
+
`
|
|
698
|
+
|
|
699
|
+
const examplePath = path.join(srcDir, 'index.eth')
|
|
700
|
+
if (!fs.existsSync(examplePath)) {
|
|
701
|
+
fs.writeFileSync(examplePath, exampleHtml)
|
|
702
|
+
logSuccess('Fichier src/index.eth créé')
|
|
703
|
+
}
|
|
704
|
+
|
|
705
|
+
const cssPath = path.join(srcDir, 'styles.eth')
|
|
706
|
+
if (!fs.existsSync(cssPath)) {
|
|
707
|
+
fs.writeFileSync(cssPath, exampleCss)
|
|
708
|
+
logSuccess('Fichier src/styles.eth créé')
|
|
709
|
+
}
|
|
710
|
+
|
|
711
|
+
const jsPath = path.join(srcDir, 'app.eth')
|
|
712
|
+
if (!fs.existsSync(jsPath)) {
|
|
713
|
+
fs.writeFileSync(jsPath, exampleJs)
|
|
714
|
+
logSuccess('Fichier src/app.eth créé')
|
|
715
|
+
}
|
|
716
|
+
|
|
717
|
+
const logoSvg = `<?xml version="1.0" encoding="UTF-8"?>
|
|
718
|
+
<svg version="1.1" xmlns="http://www.w3.org/2000/svg" viewBox="0 0 499 500">
|
|
719
|
+
<defs>
|
|
720
|
+
<linearGradient id="g1" gradientUnits="userSpaceOnUse" x1="1.106" y1="250.0527" x2="498.8623" y2="250.0527">
|
|
721
|
+
<stop offset="0" style="stop-color:#03AFC6"/><stop offset="0.5126" style="stop-color:#31BAD3"/><stop offset="1" style="stop-color:#3EC4DD"/>
|
|
722
|
+
</linearGradient>
|
|
723
|
+
<linearGradient id="g2" gradientUnits="userSpaceOnUse" x1="56.332" y1="250.0532" x2="470.3037" y2="250.0532">
|
|
724
|
+
<stop offset="0" style="stop-color:#4758A7"/><stop offset="0.2597" style="stop-color:#6F53A2"/><stop offset="0.5939" style="stop-color:#924A9D"/><stop offset="0.8518" style="stop-color:#A64399"/><stop offset="1" style="stop-color:#AE4097"/>
|
|
725
|
+
</linearGradient>
|
|
726
|
+
<linearGradient id="g3" gradientUnits="userSpaceOnUse" x1="144.1313" y1="289.1055" x2="448.2786" y2="147.2793">
|
|
727
|
+
<stop offset="0" style="stop-color:#5E6AB2"/><stop offset="0.1138" style="stop-color:#5A77B7"/><stop offset="0.4161" style="stop-color:#4D96C8"/><stop offset="0.6753" style="stop-color:#3DADD5"/><stop offset="0.8782" style="stop-color:#30BBDD"/><stop offset="1" style="stop-color:#24C1E1"/>
|
|
728
|
+
</linearGradient>
|
|
729
|
+
<linearGradient id="g4" gradientUnits="userSpaceOnUse" x1="223.2749" y1="258.8877" x2="387.1369" y2="182.4776">
|
|
730
|
+
<stop offset="0" style="stop-color:#5E6AB2"/><stop offset="0.1138" style="stop-color:#5A77B7"/><stop offset="0.4161" style="stop-color:#4D96C8"/><stop offset="0.6753" style="stop-color:#3DADD5"/><stop offset="0.8782" style="stop-color:#30BBDD"/><stop offset="1" style="stop-color:#24C1E1"/>
|
|
731
|
+
</linearGradient>
|
|
732
|
+
</defs>
|
|
733
|
+
<path fill="url(#g1)" d="M5.381,238.563c-5.468-13.48-5.838-18.365-0.256-29.006c5.582-10.641,129.113-178.821,157.852-196.679C191.716-4.979,414.415,0.938,431.034,1.795c16.621,0.856,39.463,3.869,45.858,10.234c6.395,6.365,10.242,8.502,12.175,17.908c1.932,9.406,7.304,45.665,8.664,91.185c1.362,45.521,1.822,152.92-0.505,186.578c-2.325,33.658-1.465,47.184-14.553,67.173c-13.089,19.987-218.044,111.611-232.689,117.413c-14.646,5.801-35.404,16.623-62.205-6.569C160.979,462.526,5.381,238.563,5.381,238.563z"/>
|
|
734
|
+
<path fill="url(#g2)" d="M291.136,436.653c-7.71,3.354-52.39,24.228-61.917,23.915c-10.871-0.359-16.045-4.679-36.578-31.196C157.484,383.969,67.434,258.436,60.702,243.983c-5.766-12.377-6.472-19.474,1.826-32.931c8.299-13.456,105.229-151.605,117.666-160.906c12.648-9.458,23.041-10.032,35.5-10.188c55.329-0.694,152.766-0.725,225.992,1.368c16.294,0.466,24.063,11.799,24.653,21.583c5.707,94.576,6.6,150.435-6.481,266.374c-3.107,27.537-16.15,39.634-36.553,49.895C402.604,389.586,301.741,432.036,291.136,436.653z"/>
|
|
735
|
+
<path fill="url(#g3)" d="M397.598,278.148c2.596,0.083,3.073,1.179,3.474,1.965c0.642,1.265,0.51,3.642-0.264,5.969c-0.545,1.638-10.225,33.388-13.646,43.11c-1.364,3.871-2.896,4.9-5.702,6.357c-2.692,1.396-132.598,52.559-143.222,56.099c-6.013,2.006-6.541,0.742-9.292-1.695c-2.749-2.437-107.08-151.266-109.972-155.301c-2.893-4.034-1.419-6.227-0.711-7.854c1.636-3.759,78.848-110.176,84.417-116.574c5.57-6.398,8.097-8.37,12.46-11.03c2.771-1.689,5.406-3.374,11.53-3.739c12.276-0.731,183.793-0.426,192.787,0.333c4.049,0.342,7.233,1.441,6.384,6.319c-0.853,4.879-9.874,46.584-8.671,43.199c1.204-3.385-1.01,5.33-8.432,5.348c-7.422,0.02-153.731,0-157.729,0.039c-6.107,0.06-6.991,0.443-7.952,1.426c-2.176,2.226-54.269,71.788-57.427,75.836c-3.157,4.048-2.247,5.316,1.448,9.065c3.695,3.748,69.606,62.824,69.606,62.824s-10.604-21.547-8.459-21.547"/>
|
|
736
|
+
<path fill="url(#g4)" d="M213.282,235.909c14.12-20.019,28.129-36.342,31.945-40.798c5.362-6.26,5.762-5.197,8.629-5.197c2.869,0,132.714,0.003,132.714,0.003s2.916-0.469,4.863,1.864c1.029,1.235-0.298,5.654-0.298,5.654l-9.552,40.332c0,0-1.318,5.72-6.366,5.649C334.696,242.847,208.748,250.903,213.282,235.909"/>
|
|
737
|
+
</svg>`
|
|
738
|
+
|
|
739
|
+
const logoPath = path.join(imagesDir, 'Logo_Ether.svg')
|
|
740
|
+
if (!fs.existsSync(logoPath)) {
|
|
741
|
+
fs.writeFileSync(logoPath, logoSvg)
|
|
742
|
+
logSuccess('Fichier public/images/Logo_Ether.svg créé')
|
|
743
|
+
}
|
|
744
|
+
|
|
745
|
+
console.log('')
|
|
746
|
+
logSuccess('Projet initialisé avec succès!')
|
|
747
|
+
console.log('')
|
|
748
|
+
logInfo('Prochaines étapes:')
|
|
749
|
+
console.log(` 1. Éditer ${COLORS.cyan}src/index.eth${COLORS.reset}`)
|
|
750
|
+
console.log(` 2. Lancer ${COLORS.cyan}ether dev${COLORS.reset} pour le mode développement`)
|
|
751
|
+
console.log(` 3. Ouvrir ${COLORS.cyan}http://localhost:3000${COLORS.reset} dans le navigateur`)
|
|
752
|
+
console.log('')
|
|
753
|
+
}
|
|
754
|
+
|
|
755
|
+
async function cmdBuild(options) {
|
|
756
|
+
const startTime = Date.now()
|
|
757
|
+
|
|
758
|
+
if (!options.quiet) {
|
|
759
|
+
showBanner()
|
|
760
|
+
logInfo('Compilation en cours...')
|
|
761
|
+
console.log('')
|
|
762
|
+
}
|
|
763
|
+
|
|
764
|
+
const config = loadConfig(options.config)
|
|
765
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
766
|
+
const outDir = path.resolve(process.cwd(), options.output || config.out)
|
|
767
|
+
|
|
768
|
+
if (!fs.existsSync(srcDir)) {
|
|
769
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
770
|
+
process.exit(1)
|
|
771
|
+
}
|
|
772
|
+
|
|
773
|
+
if (!fs.existsSync(outDir)) {
|
|
774
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
775
|
+
}
|
|
776
|
+
|
|
777
|
+
const compiler = new EtherCompiler(config)
|
|
778
|
+
|
|
779
|
+
const files = findEthFiles(srcDir)
|
|
780
|
+
|
|
781
|
+
if (files.length === 0) {
|
|
782
|
+
logWarning('Aucun fichier .eth trouvé')
|
|
783
|
+
return
|
|
784
|
+
}
|
|
785
|
+
|
|
786
|
+
let successCount = 0
|
|
787
|
+
let errorCount = 0
|
|
788
|
+
|
|
789
|
+
for (const file of files) {
|
|
790
|
+
try {
|
|
791
|
+
const relativePath = path.relative(srcDir, file)
|
|
792
|
+
const result = await compiler.compileFile(file)
|
|
793
|
+
|
|
794
|
+
for (const output of result.outputs) {
|
|
795
|
+
const outPath = path.join(outDir, output.path)
|
|
796
|
+
const outDirPath = path.dirname(outPath)
|
|
797
|
+
|
|
798
|
+
if (!fs.existsSync(outDirPath)) {
|
|
799
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
800
|
+
}
|
|
801
|
+
|
|
802
|
+
fs.writeFileSync(outPath, output.content)
|
|
803
|
+
|
|
804
|
+
if (options.verbose) {
|
|
805
|
+
logSuccess(`${relativePath} → ${output.path}`)
|
|
806
|
+
}
|
|
807
|
+
}
|
|
808
|
+
|
|
809
|
+
successCount++
|
|
810
|
+
} catch (err) {
|
|
811
|
+
errorCount++
|
|
812
|
+
logError(`${path.relative(srcDir, file)}: ${err.message}`)
|
|
813
|
+
|
|
814
|
+
if (options.verbose) {
|
|
815
|
+
console.error(err.stack)
|
|
816
|
+
}
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
|
|
820
|
+
const duration = Date.now() - startTime
|
|
821
|
+
|
|
822
|
+
console.log('')
|
|
823
|
+
if (errorCount === 0) {
|
|
824
|
+
logSuccess(`${successCount} fichier(s) compilé(s) en ${duration}ms`)
|
|
825
|
+
} else {
|
|
826
|
+
logWarning(`${successCount} succès, ${errorCount} erreur(s) en ${duration}ms`)
|
|
827
|
+
}
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
async function cmdDev(options) {
|
|
831
|
+
showBanner()
|
|
832
|
+
|
|
833
|
+
const config = loadConfig(options.config)
|
|
834
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
835
|
+
const outDir = path.resolve(process.cwd(), options.output || config.out)
|
|
836
|
+
const port = options.port || config.port || 3000
|
|
837
|
+
|
|
838
|
+
if (!fs.existsSync(srcDir)) {
|
|
839
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
840
|
+
process.exit(1)
|
|
841
|
+
}
|
|
842
|
+
|
|
843
|
+
if (!fs.existsSync(outDir)) {
|
|
844
|
+
fs.mkdirSync(outDir, { recursive: true })
|
|
845
|
+
}
|
|
846
|
+
|
|
847
|
+
logInfo(`Surveillance de ${srcDir}`)
|
|
848
|
+
logInfo(`Sortie vers ${outDir}`)
|
|
849
|
+
console.log('')
|
|
850
|
+
|
|
851
|
+
await cmdBuild({ ...options, quiet: true })
|
|
852
|
+
|
|
853
|
+
let devServer = null
|
|
854
|
+
if (!options.noServer) {
|
|
855
|
+
devServer = createDevServer(outDir, port)
|
|
856
|
+
}
|
|
857
|
+
|
|
858
|
+
console.log('')
|
|
859
|
+
logInfo('En attente de modifications... (Ctrl+C pour arrêter)')
|
|
860
|
+
console.log('')
|
|
861
|
+
|
|
862
|
+
const watcher = new Watcher(srcDir, config.watch)
|
|
863
|
+
const compiler = new EtherCompiler(config)
|
|
864
|
+
|
|
865
|
+
watcher.on('change', async (filePath) => {
|
|
866
|
+
if (!filePath.endsWith('.eth')) return
|
|
867
|
+
|
|
868
|
+
const relativePath = path.relative(srcDir, filePath)
|
|
869
|
+
logInfo(`Modification: ${relativePath}`)
|
|
870
|
+
|
|
871
|
+
try {
|
|
872
|
+
const result = await compiler.compileFile(filePath)
|
|
873
|
+
|
|
874
|
+
for (const output of result.outputs) {
|
|
875
|
+
const outPath = path.join(outDir, output.path)
|
|
876
|
+
const outDirPath = path.dirname(outPath)
|
|
877
|
+
|
|
878
|
+
if (!fs.existsSync(outDirPath)) {
|
|
879
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
880
|
+
}
|
|
881
|
+
|
|
882
|
+
fs.writeFileSync(outPath, output.content)
|
|
883
|
+
logSuccess(`→ ${output.path}`)
|
|
884
|
+
}
|
|
885
|
+
|
|
886
|
+
if (devServer) {
|
|
887
|
+
devServer.reload()
|
|
888
|
+
}
|
|
889
|
+
} catch (err) {
|
|
890
|
+
logError(err.message)
|
|
891
|
+
}
|
|
892
|
+
})
|
|
893
|
+
|
|
894
|
+
watcher.on('add', async (filePath) => {
|
|
895
|
+
if (!filePath.endsWith('.eth')) return
|
|
896
|
+
|
|
897
|
+
const relativePath = path.relative(srcDir, filePath)
|
|
898
|
+
logInfo(`Nouveau fichier: ${relativePath}`)
|
|
899
|
+
|
|
900
|
+
try {
|
|
901
|
+
const result = await compiler.compileFile(filePath)
|
|
902
|
+
|
|
903
|
+
for (const output of result.outputs) {
|
|
904
|
+
const outPath = path.join(outDir, output.path)
|
|
905
|
+
const outDirPath = path.dirname(outPath)
|
|
906
|
+
|
|
907
|
+
if (!fs.existsSync(outDirPath)) {
|
|
908
|
+
fs.mkdirSync(outDirPath, { recursive: true })
|
|
909
|
+
}
|
|
910
|
+
|
|
911
|
+
fs.writeFileSync(outPath, output.content)
|
|
912
|
+
logSuccess(`→ ${output.path}`)
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
if (devServer) {
|
|
916
|
+
devServer.reload()
|
|
917
|
+
}
|
|
918
|
+
} catch (err) {
|
|
919
|
+
logError(err.message)
|
|
920
|
+
}
|
|
921
|
+
})
|
|
922
|
+
|
|
923
|
+
watcher.on('unlink', (filePath) => {
|
|
924
|
+
if (!filePath.endsWith('.eth')) return
|
|
925
|
+
logWarning(`Fichier supprimé: ${path.relative(srcDir, filePath)}`)
|
|
926
|
+
})
|
|
927
|
+
|
|
928
|
+
watcher.start()
|
|
929
|
+
|
|
930
|
+
process.on('SIGINT', () => {
|
|
931
|
+
console.log('')
|
|
932
|
+
logInfo('Arrêt du mode développement')
|
|
933
|
+
watcher.stop()
|
|
934
|
+
if (devServer) {
|
|
935
|
+
devServer.close()
|
|
936
|
+
}
|
|
937
|
+
process.exit(0)
|
|
938
|
+
})
|
|
939
|
+
}
|
|
940
|
+
|
|
941
|
+
async function cmdCheck(options) {
|
|
942
|
+
showBanner()
|
|
943
|
+
logInfo('Vérification de la syntaxe...')
|
|
944
|
+
console.log('')
|
|
945
|
+
|
|
946
|
+
const config = loadConfig(options.config)
|
|
947
|
+
const srcDir = path.resolve(process.cwd(), config.src)
|
|
948
|
+
|
|
949
|
+
if (!fs.existsSync(srcDir)) {
|
|
950
|
+
logError(`Dossier source introuvable: ${srcDir}`)
|
|
951
|
+
process.exit(1)
|
|
952
|
+
}
|
|
953
|
+
|
|
954
|
+
const compiler = new EtherCompiler(config)
|
|
955
|
+
const files = findEthFiles(srcDir)
|
|
956
|
+
|
|
957
|
+
if (files.length === 0) {
|
|
958
|
+
logWarning('Aucun fichier .eth trouvé')
|
|
959
|
+
return
|
|
960
|
+
}
|
|
961
|
+
|
|
962
|
+
let validCount = 0
|
|
963
|
+
let errorCount = 0
|
|
964
|
+
|
|
965
|
+
for (const file of files) {
|
|
966
|
+
const relativePath = path.relative(srcDir, file)
|
|
967
|
+
|
|
968
|
+
try {
|
|
969
|
+
await compiler.check(file)
|
|
970
|
+
logSuccess(relativePath)
|
|
971
|
+
validCount++
|
|
972
|
+
} catch (err) {
|
|
973
|
+
logError(`${relativePath}: ${err.message}`)
|
|
974
|
+
errorCount++
|
|
975
|
+
}
|
|
976
|
+
}
|
|
977
|
+
|
|
978
|
+
console.log('')
|
|
979
|
+
if (errorCount === 0) {
|
|
980
|
+
logSuccess(`${validCount} fichier(s) valide(s)`)
|
|
981
|
+
} else {
|
|
982
|
+
logError(`${errorCount} erreur(s) sur ${files.length} fichier(s)`)
|
|
983
|
+
process.exit(1)
|
|
984
|
+
}
|
|
985
|
+
}
|
|
986
|
+
|
|
987
|
+
function findEthFiles(dir) {
|
|
988
|
+
const files = []
|
|
989
|
+
|
|
990
|
+
function scan(currentDir) {
|
|
991
|
+
const entries = fs.readdirSync(currentDir, { withFileTypes: true })
|
|
992
|
+
|
|
993
|
+
for (const entry of entries) {
|
|
994
|
+
const fullPath = path.join(currentDir, entry.name)
|
|
995
|
+
|
|
996
|
+
if (entry.isDirectory()) {
|
|
997
|
+
if (!['node_modules', '.git', 'dist'].includes(entry.name)) {
|
|
998
|
+
scan(fullPath)
|
|
999
|
+
}
|
|
1000
|
+
} else if (entry.name.endsWith('.eth')) {
|
|
1001
|
+
files.push(fullPath)
|
|
1002
|
+
}
|
|
1003
|
+
}
|
|
1004
|
+
}
|
|
1005
|
+
|
|
1006
|
+
scan(dir)
|
|
1007
|
+
return files
|
|
1008
|
+
}
|
|
1009
|
+
|
|
1010
|
+
function parseArgs(args) {
|
|
1011
|
+
const options = {
|
|
1012
|
+
command: null,
|
|
1013
|
+
config: null,
|
|
1014
|
+
output: null,
|
|
1015
|
+
port: null,
|
|
1016
|
+
verbose: false,
|
|
1017
|
+
quiet: false,
|
|
1018
|
+
watch: false,
|
|
1019
|
+
noServer: false
|
|
1020
|
+
}
|
|
1021
|
+
|
|
1022
|
+
let i = 0
|
|
1023
|
+
while (i < args.length) {
|
|
1024
|
+
const arg = args[i]
|
|
1025
|
+
|
|
1026
|
+
if (arg === '-c' || arg === '--config') {
|
|
1027
|
+
options.config = args[++i]
|
|
1028
|
+
} else if (arg === '-o' || arg === '--output') {
|
|
1029
|
+
options.output = args[++i]
|
|
1030
|
+
} else if (arg === '-p' || arg === '--port') {
|
|
1031
|
+
options.port = parseInt(args[++i], 10)
|
|
1032
|
+
} else if (arg === '-v' || arg === '--verbose') {
|
|
1033
|
+
options.verbose = true
|
|
1034
|
+
} else if (arg === '-q' || arg === '--quiet') {
|
|
1035
|
+
options.quiet = true
|
|
1036
|
+
} else if (arg === '-w' || arg === '--watch') {
|
|
1037
|
+
options.watch = true
|
|
1038
|
+
} else if (arg === '--no-color') {
|
|
1039
|
+
for (const key in COLORS) {
|
|
1040
|
+
COLORS[key] = ''
|
|
1041
|
+
}
|
|
1042
|
+
} else if (arg === '--no-server') {
|
|
1043
|
+
options.noServer = true
|
|
1044
|
+
} else if (!arg.startsWith('-')) {
|
|
1045
|
+
if (!options.command) {
|
|
1046
|
+
options.command = arg
|
|
1047
|
+
}
|
|
1048
|
+
}
|
|
1049
|
+
|
|
1050
|
+
i++
|
|
1051
|
+
}
|
|
1052
|
+
|
|
1053
|
+
return options
|
|
1054
|
+
}
|
|
1055
|
+
|
|
1056
|
+
async function main() {
|
|
1057
|
+
const args = process.argv.slice(2)
|
|
1058
|
+
const options = parseArgs(args)
|
|
1059
|
+
|
|
1060
|
+
if (options.watch && !options.command) {
|
|
1061
|
+
options.command = 'dev'
|
|
1062
|
+
}
|
|
1063
|
+
|
|
1064
|
+
switch (options.command) {
|
|
1065
|
+
case 'init':
|
|
1066
|
+
await cmdInit()
|
|
1067
|
+
break
|
|
1068
|
+
case 'build':
|
|
1069
|
+
await cmdBuild(options)
|
|
1070
|
+
break
|
|
1071
|
+
case 'dev':
|
|
1072
|
+
case 'watch':
|
|
1073
|
+
await cmdDev(options)
|
|
1074
|
+
break
|
|
1075
|
+
case 'check':
|
|
1076
|
+
await cmdCheck(options)
|
|
1077
|
+
break
|
|
1078
|
+
case 'version':
|
|
1079
|
+
case '-V':
|
|
1080
|
+
case '--version':
|
|
1081
|
+
showVersion()
|
|
1082
|
+
break
|
|
1083
|
+
case 'help':
|
|
1084
|
+
case '-h':
|
|
1085
|
+
case '--help':
|
|
1086
|
+
default:
|
|
1087
|
+
showHelp()
|
|
1088
|
+
break
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
|
|
1092
|
+
main().catch(err => {
|
|
1093
|
+
logError(err.message)
|
|
1094
|
+
process.exit(1)
|
|
1095
|
+
})
|