coralite-scripts 0.40.2 → 0.41.0
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 +17 -1
- package/bin/index.js +3 -3
- package/libs/server.js +150 -143
- package/package.json +2 -2
package/README.md
CHANGED
|
@@ -72,7 +72,8 @@ Update your `package.json` scripts to include:
|
|
|
72
72
|
```json
|
|
73
73
|
{
|
|
74
74
|
"scripts": {
|
|
75
|
-
"start": "coralite-scripts
|
|
75
|
+
"start": "coralite-scripts dev",
|
|
76
|
+
"test": "coralite-scripts test",
|
|
76
77
|
"build": "coralite-scripts build"
|
|
77
78
|
}
|
|
78
79
|
}
|
|
@@ -88,6 +89,21 @@ npm run start
|
|
|
88
89
|
|
|
89
90
|
---
|
|
90
91
|
|
|
92
|
+
## Test Mode
|
|
93
|
+
|
|
94
|
+
Coralite Scripts includes a dedicated mode for automated testing (e.g., E2E testing with Playwright or Cypress).
|
|
95
|
+
|
|
96
|
+
```bash
|
|
97
|
+
npm run test
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
When running in `test` mode:
|
|
101
|
+
- **`testing` mode enabled**: Sets Coralite's internal mode to `testing`, which enables deterministic IDs, telemetry, and disables CSS animations/transitions to speed up tests.
|
|
102
|
+
- **No Hot Reloading**: Disables Server-Sent Events (SSE) and live reload script injection for a stable testing environment.
|
|
103
|
+
- **No File Watching**: Disables the file watcher to reduce resource usage during CI/CD pipelines.
|
|
104
|
+
|
|
105
|
+
---
|
|
106
|
+
|
|
91
107
|
## Live Development Features
|
|
92
108
|
|
|
93
109
|
Coralite provides real-time development workflows out of the box:
|
package/bin/index.js
CHANGED
|
@@ -18,7 +18,7 @@ program
|
|
|
18
18
|
.name('Coralite scripts')
|
|
19
19
|
.description(pkg.description)
|
|
20
20
|
.version(pkg.version)
|
|
21
|
-
.addArgument(new Argument('<mode>', 'Run mode: dev (development server) or build (production compilation)').choices(['dev', 'build']).default('dev'))
|
|
21
|
+
.addArgument(new Argument('<mode>', 'Run mode: dev (development server), test (testing server) or build (production compilation)').choices(['dev', 'test', 'build']).default('dev'))
|
|
22
22
|
.option('-v, --verbose', 'Enable verbose logging output')
|
|
23
23
|
.option('-c, --clean', 'Clear the output directory before building')
|
|
24
24
|
.option('-a, --assets <mapping...>', 'Static assets to copy during build. Format: pkg:path:dest or src:dest')
|
|
@@ -46,11 +46,11 @@ if (options.assets) {
|
|
|
46
46
|
}
|
|
47
47
|
}
|
|
48
48
|
|
|
49
|
-
if (mode === 'dev') {
|
|
49
|
+
if (mode === 'dev' || mode === 'test') {
|
|
50
50
|
config.output = join(process.cwd(), '.coralite')
|
|
51
51
|
await mkdir(config.output, { recursive: true })
|
|
52
52
|
|
|
53
|
-
await server(config, options)
|
|
53
|
+
await server(config, options, mode)
|
|
54
54
|
} else if (mode === 'build') {
|
|
55
55
|
try {
|
|
56
56
|
await buildCommand(config, options)
|
package/libs/server.js
CHANGED
|
@@ -123,9 +123,10 @@ export async function resolveSource (reqPath, extension, config, coralite, memor
|
|
|
123
123
|
* Starts a development server with hot-reloading capabilities
|
|
124
124
|
* @param {CoraliteScriptConfig} config - Coralite configuration
|
|
125
125
|
* @param {CoraliteScriptOptions} options - Coralite configuration
|
|
126
|
+
* @param {string} runMode - The run mode (dev or test)
|
|
126
127
|
* @returns {Promise<void>}
|
|
127
128
|
*/
|
|
128
|
-
async function server (config, options) {
|
|
129
|
+
async function server (config, options, runMode = 'dev') {
|
|
129
130
|
try {
|
|
130
131
|
const app = express()
|
|
131
132
|
const startPort = config.server?.port && !isNaN(config.server.port) ? config.server.port : 3000
|
|
@@ -218,7 +219,7 @@ async function server (config, options) {
|
|
|
218
219
|
baseURL: currentConfig.baseURL,
|
|
219
220
|
ignoreByAttribute: currentConfig.ignoreByAttribute,
|
|
220
221
|
skipRenderByAttribute: currentConfig.skipRenderByAttribute,
|
|
221
|
-
mode: 'development',
|
|
222
|
+
mode: runMode === 'test' ? 'testing' : 'development',
|
|
222
223
|
output: currentConfig.output,
|
|
223
224
|
onError: ({ level, message, error }) => {
|
|
224
225
|
if (level === 'ERR') {
|
|
@@ -380,15 +381,19 @@ async function server (config, options) {
|
|
|
380
381
|
const start = process.hrtime()
|
|
381
382
|
let duration, dash = colours.gray(' ─ ')
|
|
382
383
|
|
|
383
|
-
let rebuildScript = '
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
389
|
-
|
|
390
|
-
|
|
391
|
-
|
|
384
|
+
let rebuildScript = '</body>\n'
|
|
385
|
+
|
|
386
|
+
if (runMode !== 'test') {
|
|
387
|
+
rebuildScript = '\n<script>\n'
|
|
388
|
+
rebuildScript += " const eventSource = new EventSource('/_/rebuild');\n"
|
|
389
|
+
rebuildScript += ' eventSource.onmessage = function(event) {\n'
|
|
390
|
+
rebuildScript += " if (event.data === 'connected') return;\n"
|
|
391
|
+
rebuildScript += ' // Reload page when file changes\n'
|
|
392
|
+
rebuildScript += ' location.reload()\n'
|
|
393
|
+
rebuildScript += ' }\n'
|
|
394
|
+
rebuildScript += ' </script>\n'
|
|
395
|
+
rebuildScript += '</body>\n'
|
|
396
|
+
}
|
|
392
397
|
|
|
393
398
|
// Only set item if it's not already in the collection (virtual pages are pre-registered)
|
|
394
399
|
const item = coralite.pages.getItem(pathname)
|
|
@@ -467,159 +472,161 @@ async function server (config, options) {
|
|
|
467
472
|
}
|
|
468
473
|
)
|
|
469
474
|
|
|
470
|
-
|
|
471
|
-
|
|
472
|
-
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
|
|
475
|
+
if (runMode !== 'test') {
|
|
476
|
+
// watch for file changes
|
|
477
|
+
const watcher = chokidar.watch(watchPath, {
|
|
478
|
+
persistent: true,
|
|
479
|
+
// Add ignoreInitial to prevent initial scan events
|
|
480
|
+
ignoreInitial: true
|
|
481
|
+
})
|
|
476
482
|
|
|
477
|
-
|
|
478
|
-
|
|
483
|
+
const componentPath = normalize(config.components)
|
|
484
|
+
const pagesPath = normalize(config.pages)
|
|
479
485
|
|
|
480
|
-
|
|
481
|
-
|
|
482
|
-
|
|
483
|
-
|
|
484
|
-
|
|
485
|
-
|
|
486
|
+
// Debouncing and compilation state management
|
|
487
|
+
let compileTimeout = null
|
|
488
|
+
let isCompiling = false
|
|
489
|
+
const pendingChanges = new Set()
|
|
490
|
+
const structuralChanges = new Set()
|
|
491
|
+
const configPathStr = join(process.cwd(), 'coralite.config.js')
|
|
486
492
|
|
|
487
493
|
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
}
|
|
494
|
-
compileTimeout = setTimeout(async () => {
|
|
495
|
-
if (isCompiling || pendingChanges.size === 0) {
|
|
496
|
-
return
|
|
494
|
+
// Helper function to debounce compilations
|
|
495
|
+
const debounceCompile = () => {
|
|
496
|
+
if (compileTimeout) {
|
|
497
|
+
// @ts-ignore
|
|
498
|
+
clearTimeout(compileTimeout)
|
|
497
499
|
}
|
|
500
|
+
compileTimeout = setTimeout(async () => {
|
|
501
|
+
if (isCompiling || pendingChanges.size === 0) {
|
|
502
|
+
return
|
|
503
|
+
}
|
|
498
504
|
|
|
499
|
-
|
|
500
|
-
|
|
501
|
-
isCompiling = true
|
|
502
|
-
let dash = colours.gray(' ─ ')
|
|
505
|
+
pageCache.clear()
|
|
503
506
|
|
|
504
|
-
|
|
505
|
-
|
|
506
|
-
pendingChanges.clear()
|
|
507
|
-
const isStructural = structuralChanges.size > 0
|
|
508
|
-
structuralChanges.clear()
|
|
507
|
+
isCompiling = true
|
|
508
|
+
let dash = colours.gray(' ─ ')
|
|
509
509
|
|
|
510
|
-
|
|
510
|
+
// Process all pending changes
|
|
511
|
+
const changes = Array.from(pendingChanges)
|
|
512
|
+
pendingChanges.clear()
|
|
513
|
+
const isStructural = structuralChanges.size > 0
|
|
514
|
+
structuralChanges.clear()
|
|
511
515
|
|
|
512
|
-
|
|
513
|
-
const pagesChanges = changes.filter(p => p.startsWith(pagesPath))
|
|
514
|
-
const componentChanges = changes.filter(p => p.startsWith(componentPath))
|
|
515
|
-
const stylesChanges = changes.filter(p => {
|
|
516
|
-
if (!currentConfig.styles?.input) {
|
|
517
|
-
return false
|
|
518
|
-
}
|
|
519
|
-
return currentConfig.styles.input.some(input => p.startsWith(normalize(join(process.cwd(), dirname(input))))) || p.endsWith('.scss') || p.endsWith('.sass') || p.endsWith('.css')
|
|
520
|
-
})
|
|
521
|
-
const configChanges = changes.filter(p => p === configPathStr || Array.from(pluginPaths).some(pluginPath => p === pluginPath))
|
|
516
|
+
await coralite.clearCache(isStructural)
|
|
522
517
|
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
|
|
527
|
-
|
|
528
|
-
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
|
|
532
|
-
|
|
533
|
-
|
|
534
|
-
|
|
535
|
-
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
|
|
539
|
-
|
|
540
|
-
|
|
541
|
-
|
|
518
|
+
// Group changes by type
|
|
519
|
+
const pagesChanges = changes.filter(p => p.startsWith(pagesPath))
|
|
520
|
+
const componentChanges = changes.filter(p => p.startsWith(componentPath))
|
|
521
|
+
const stylesChanges = changes.filter(p => {
|
|
522
|
+
if (!currentConfig.styles?.input) {
|
|
523
|
+
return false
|
|
524
|
+
}
|
|
525
|
+
return currentConfig.styles.input.some(input => p.startsWith(normalize(join(process.cwd(), dirname(input))))) || p.endsWith('.scss') || p.endsWith('.sass') || p.endsWith('.css')
|
|
526
|
+
})
|
|
527
|
+
const configChanges = changes.filter(p => p === configPathStr || Array.from(pluginPaths).some(pluginPath => p === pluginPath))
|
|
528
|
+
|
|
529
|
+
try {
|
|
530
|
+
// Handle config changes
|
|
531
|
+
if (configChanges.length > 0) {
|
|
532
|
+
displayInfo('Configuration changed, reloading Coralite...')
|
|
533
|
+
// Append cache busting param to reload properly
|
|
534
|
+
const { pathToFileURL } = await import('url')
|
|
535
|
+
const bust = '?t=' + Date.now()
|
|
536
|
+
|
|
537
|
+
try {
|
|
538
|
+
const freshConfigModule = await import(pathToFileURL(configPathStr).toString() + bust)
|
|
539
|
+
const { defineConfig } = await import('./config.js')
|
|
540
|
+
|
|
541
|
+
if (freshConfigModule.default) {
|
|
542
|
+
currentConfig = defineConfig(freshConfigModule.default)
|
|
543
|
+
currentConfig.output = config.output
|
|
544
|
+
currentConfig.server = config.server
|
|
545
|
+
|
|
546
|
+
// Re-initialize Coralite with new config
|
|
547
|
+
await initCoralite()
|
|
548
|
+
}
|
|
549
|
+
} catch (err) {
|
|
550
|
+
displayError('Failed to reload configuration', err)
|
|
542
551
|
}
|
|
543
|
-
} catch (err) {
|
|
544
|
-
displayError('Failed to reload configuration', err)
|
|
545
552
|
}
|
|
546
|
-
}
|
|
547
553
|
|
|
548
|
-
|
|
549
|
-
|
|
550
|
-
|
|
551
|
-
|
|
554
|
+
// Handle component changes
|
|
555
|
+
for (const path of componentChanges) {
|
|
556
|
+
await coralite.components.setItem(path)
|
|
557
|
+
}
|
|
552
558
|
|
|
553
|
-
|
|
554
|
-
|
|
555
|
-
|
|
556
|
-
|
|
557
|
-
|
|
558
|
-
|
|
559
|
-
|
|
559
|
+
// Handle STYLES changes - rebuild all STYLES files once
|
|
560
|
+
if (stylesChanges.length > 0 && currentConfig.styles?.input) {
|
|
561
|
+
const results = await buildStyles({
|
|
562
|
+
input: currentConfig.styles.input,
|
|
563
|
+
processors: currentConfig.styles.processors,
|
|
564
|
+
output: join(config.output, 'assets', 'css')
|
|
565
|
+
})
|
|
560
566
|
|
|
561
|
-
|
|
562
|
-
|
|
567
|
+
for (const result of results) {
|
|
568
|
+
process.stdout.write(toTime() + colours.bgGreen(' Compiled STYLES ') + dash + toMS(result.duration) + dash + result.input + '\n')
|
|
569
|
+
}
|
|
563
570
|
}
|
|
564
|
-
}
|
|
565
571
|
|
|
566
|
-
|
|
567
|
-
|
|
568
|
-
|
|
569
|
-
|
|
570
|
-
|
|
571
|
-
|
|
572
|
-
|
|
573
|
-
|
|
572
|
+
// Notify clients to reload
|
|
573
|
+
if (pagesChanges.length > 0
|
|
574
|
+
|| componentChanges.length > 0
|
|
575
|
+
|| stylesChanges.length > 0
|
|
576
|
+
|| configChanges.length > 0) {
|
|
577
|
+
clients.forEach(client => {
|
|
578
|
+
client.write(`data: reload\n\n`)
|
|
579
|
+
})
|
|
580
|
+
}
|
|
581
|
+
} catch (error) {
|
|
582
|
+
displayError('Compilation failed', error)
|
|
583
|
+
} finally {
|
|
584
|
+
isCompiling = false
|
|
574
585
|
}
|
|
575
|
-
}
|
|
576
|
-
|
|
577
|
-
} finally {
|
|
578
|
-
isCompiling = false
|
|
579
|
-
}
|
|
580
|
-
}, 100)
|
|
581
|
-
}
|
|
586
|
+
}, 100)
|
|
587
|
+
}
|
|
582
588
|
|
|
583
|
-
|
|
584
|
-
|
|
585
|
-
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
589
|
+
watcher
|
|
590
|
+
.on('unlink', async (path) => {
|
|
591
|
+
try {
|
|
592
|
+
if (path.startsWith(componentPath)) {
|
|
593
|
+
structuralChanges.add(path)
|
|
594
|
+
pendingChanges.add(path)
|
|
595
|
+
debounceCompile()
|
|
596
|
+
await coralite.components.deleteItem(path)
|
|
597
|
+
} else if (path.startsWith(pagesPath)) {
|
|
598
|
+
await coralite.pages.deleteItem(path)
|
|
599
|
+
}
|
|
600
|
+
} catch (error) {
|
|
601
|
+
displayError(`Failed to handle file deletion: ${path}`, error)
|
|
593
602
|
}
|
|
594
|
-
}
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
603
|
+
})
|
|
604
|
+
.on('change', async (path) => {
|
|
605
|
+
// We only want to trigger for things we care about or are in watchPath (but sometimes chokidar watches the whole dir)
|
|
606
|
+
pendingChanges.add(path)
|
|
607
|
+
debounceCompile()
|
|
608
|
+
})
|
|
609
|
+
.on('add', async (path) => {
|
|
610
|
+
try {
|
|
611
|
+
if (path.startsWith(componentPath)) {
|
|
612
|
+
// set component item
|
|
613
|
+
structuralChanges.add(path)
|
|
614
|
+
pendingChanges.add(path)
|
|
615
|
+
debounceCompile()
|
|
616
|
+
coralite.components.setItem(path)
|
|
617
|
+
} else if (path.endsWith('.scss') || path.endsWith('.sass') || path === configPathStr || Array.from(pluginPaths).some(pluginPath => path === pluginPath)) {
|
|
618
|
+
// Add to pending changes and trigger debounced compilation
|
|
619
|
+
pendingChanges.add(path)
|
|
620
|
+
debounceCompile()
|
|
621
|
+
}
|
|
622
|
+
} catch (error) {
|
|
623
|
+
displayError(`Failed to handle file addition: ${path}`, error)
|
|
615
624
|
}
|
|
616
|
-
}
|
|
617
|
-
|
|
618
|
-
|
|
619
|
-
|
|
620
|
-
|
|
621
|
-
displayError('File watcher error', error)
|
|
622
|
-
})
|
|
625
|
+
})
|
|
626
|
+
.on('error', (error) => {
|
|
627
|
+
displayError('File watcher error', error)
|
|
628
|
+
})
|
|
629
|
+
}
|
|
623
630
|
|
|
624
631
|
const port = await portfinder.getPortPromise({
|
|
625
632
|
port: startPort,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "coralite-scripts",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.41.0",
|
|
4
4
|
"description": "Configuration and scripts for Create Coralite.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -61,7 +61,7 @@
|
|
|
61
61
|
"portfinder": "^1.0.38",
|
|
62
62
|
"postcss": "^8.5.15",
|
|
63
63
|
"sass": "^1.101.0",
|
|
64
|
-
"coralite": "0.
|
|
64
|
+
"coralite": "0.41.0"
|
|
65
65
|
},
|
|
66
66
|
"scripts": {
|
|
67
67
|
"build": "premove dist && pnpm build:types",
|