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/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
+ "&lt;!DOCTYPE html&gt;\\n&lt;html&gt;\\n &lt;body&gt;\\n &lt;h1&gt;Bonjour&lt;/h1&gt;\\n &lt;p&gt;Le monde&lt;/p&gt;\\n &lt;/body&gt;\\n&lt;/html&gt;"
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
+ })