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 CHANGED
@@ -72,7 +72,8 @@ Update your `package.json` scripts to include:
72
72
  ```json
73
73
  {
74
74
  "scripts": {
75
- "start": "coralite-scripts start",
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 = '\n<script>\n'
384
- rebuildScript += " const eventSource = new EventSource('/_/rebuild');\n"
385
- rebuildScript += ' eventSource.onmessage = function(event) {\n'
386
- rebuildScript += " if (event.data === 'connected') return;\n"
387
- rebuildScript += ' // Reload page when file changes\n'
388
- rebuildScript += ' location.reload()\n'
389
- rebuildScript += ' }\n'
390
- rebuildScript += ' </script>\n'
391
- rebuildScript += '</body>\n'
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
- // watch for file changes
471
- const watcher = chokidar.watch(watchPath, {
472
- persistent: true,
473
- // Add ignoreInitial to prevent initial scan events
474
- ignoreInitial: true
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
- const componentPath = normalize(config.components)
478
- const pagesPath = normalize(config.pages)
483
+ const componentPath = normalize(config.components)
484
+ const pagesPath = normalize(config.pages)
479
485
 
480
- // Debouncing and compilation state management
481
- let compileTimeout = null
482
- let isCompiling = false
483
- const pendingChanges = new Set()
484
- const structuralChanges = new Set()
485
- const configPathStr = join(process.cwd(), 'coralite.config.js')
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
- // Helper function to debounce compilations
489
- const debounceCompile = () => {
490
- if (compileTimeout) {
491
- // @ts-ignore
492
- clearTimeout(compileTimeout)
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
- pageCache.clear()
500
-
501
- isCompiling = true
502
- let dash = colours.gray(' ─ ')
505
+ pageCache.clear()
503
506
 
504
- // Process all pending changes
505
- const changes = Array.from(pendingChanges)
506
- pendingChanges.clear()
507
- const isStructural = structuralChanges.size > 0
508
- structuralChanges.clear()
507
+ isCompiling = true
508
+ let dash = colours.gray(' ─ ')
509
509
 
510
- await coralite.clearCache(isStructural)
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
- // Group changes by type
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
- try {
524
- // Handle config changes
525
- if (configChanges.length > 0) {
526
- displayInfo('Configuration changed, reloading Coralite...')
527
- // Append cache busting param to reload properly
528
- const { pathToFileURL } = await import('url')
529
- const bust = '?t=' + Date.now()
530
-
531
- try {
532
- const freshConfigModule = await import(pathToFileURL(configPathStr).toString() + bust)
533
- const { defineConfig } = await import('./config.js')
534
-
535
- if (freshConfigModule.default) {
536
- currentConfig = defineConfig(freshConfigModule.default)
537
- currentConfig.output = config.output
538
- currentConfig.server = config.server
539
-
540
- // Re-initialize Coralite with new config
541
- await initCoralite()
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
- // Handle component changes
549
- for (const path of componentChanges) {
550
- await coralite.components.setItem(path)
551
- }
554
+ // Handle component changes
555
+ for (const path of componentChanges) {
556
+ await coralite.components.setItem(path)
557
+ }
552
558
 
553
- // Handle STYLES changes - rebuild all STYLES files once
554
- if (stylesChanges.length > 0 && currentConfig.styles?.input) {
555
- const results = await buildStyles({
556
- input: currentConfig.styles.input,
557
- processors: currentConfig.styles.processors,
558
- output: join(config.output, 'assets', 'css')
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
- for (const result of results) {
562
- process.stdout.write(toTime() + colours.bgGreen(' Compiled STYLES ') + dash + toMS(result.duration) + dash + result.input + '\n')
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
- // Notify clients to reload
567
- if (pagesChanges.length > 0
568
- || componentChanges.length > 0
569
- || stylesChanges.length > 0
570
- || configChanges.length > 0) {
571
- clients.forEach(client => {
572
- client.write(`data: reload\n\n`)
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
- } catch (error) {
576
- displayError('Compilation failed', error)
577
- } finally {
578
- isCompiling = false
579
- }
580
- }, 100)
581
- }
586
+ }, 100)
587
+ }
582
588
 
583
- watcher
584
- .on('unlink', async (path) => {
585
- try {
586
- if (path.startsWith(componentPath)) {
587
- structuralChanges.add(path)
588
- pendingChanges.add(path)
589
- debounceCompile()
590
- await coralite.components.deleteItem(path)
591
- } else if (path.startsWith(pagesPath)) {
592
- await coralite.pages.deleteItem(path)
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
- } catch (error) {
595
- displayError(`Failed to handle file deletion: ${path}`, error)
596
- }
597
- })
598
- .on('change', async (path) => {
599
- // We only want to trigger for things we care about or are in watchPath (but sometimes chokidar watches the whole dir)
600
- pendingChanges.add(path)
601
- debounceCompile()
602
- })
603
- .on('add', async (path) => {
604
- try {
605
- if (path.startsWith(componentPath)) {
606
- // set component item
607
- structuralChanges.add(path)
608
- pendingChanges.add(path)
609
- debounceCompile()
610
- coralite.components.setItem(path)
611
- } else if (path.endsWith('.scss') || path.endsWith('.sass') || path === configPathStr || Array.from(pluginPaths).some(pluginPath => path === pluginPath)) {
612
- // Add to pending changes and trigger debounced compilation
613
- pendingChanges.add(path)
614
- debounceCompile()
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
- } catch (error) {
617
- displayError(`Failed to handle file addition: ${path}`, error)
618
- }
619
- })
620
- .on('error', (error) => {
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.40.2",
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.40.2"
64
+ "coralite": "0.41.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "premove dist && pnpm build:types",