coralite-scripts 0.0.1

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 ADDED
@@ -0,0 +1,124 @@
1
+ # Coralite Development Environment Guide
2
+
3
+ Welcome to **Coralite starter script**, a lightweight Static Site Generator (SSG) built for rapid development and clean output. This guide walks you through setting up your local development environment using the provided `coralite-scripts` package and configuration files.
4
+
5
+ ---
6
+
7
+ ## Prerequisites
8
+
9
+ Ensure you have:
10
+ - Node.js ≥ 20.x
11
+ - npm or pnpm installed
12
+
13
+ > Coralite uses experimental ES modules (`--experimental-vm-modules`) — make sure your Node version supports it.
14
+
15
+ ---
16
+
17
+ ## Project Structure
18
+
19
+ Coralite expects a standard folder layout:
20
+
21
+ ```
22
+ my-coralite-site/
23
+ ├── src/
24
+ │ ├── pages/ # Your page templates (e.g., `about.html`, `index.html`)
25
+ │ ├── scss/ # SCSS/Sass styles
26
+ │ └── templates/ # Reusable template files
27
+ ├── public/ # Static assets (CSS, JS, images)
28
+ ├── dist/ # Output directory for built site (auto-generated)
29
+ ├── coralite.config.js # Configuration file
30
+ └── package.json # Scripts & dependencies
31
+ ```
32
+
33
+ ---
34
+
35
+ ## Step 1: Configure `coralite.config.js`
36
+
37
+ Create or update your config file with the following:
38
+
39
+ ```js
40
+ import { defineConfig } from 'coralite-scripts'
41
+
42
+ export default defineConfig({
43
+ output: 'dist',
44
+ public: 'public',
45
+ pages: 'src/pages',
46
+ templates: 'src/templates',
47
+ sass: {
48
+ input: 'src/scss'
49
+ }
50
+ })
51
+ ```
52
+
53
+ > This tells Coralite where to find your source files, compile CSS from SCSS, and serve static assets.
54
+
55
+ ---
56
+
57
+ ## Step 2: Start the Development Server
58
+
59
+ Update your `package.json` scripts to include:
60
+
61
+ ```json package.json
62
+ {
63
+ "scripts": {
64
+ "start": "coralite-script"
65
+ }
66
+ }
67
+ ```
68
+
69
+ Then start the dev server:
70
+
71
+ ```bash
72
+ npm run start
73
+ ```
74
+
75
+ > ✅ The server runs on `http://localhost:3000` by default.
76
+
77
+ ---
78
+
79
+ ## Live Development Features
80
+
81
+ Coralite provides real-time development workflows out of the box:
82
+
83
+ | Feature | How It Works |
84
+ |-------|-------------|
85
+ | **Live Reload** | Automatically reloads browser when any `.html`, `.scss`, or `.sass` file changes. |
86
+ | **Hot CSS Updates** | Sass/SCSS files are compiled instantly and injected into your page via Server-Sent Events (SSE). |
87
+ | **File Watching** | Monitors `src/pages`, `src/scss`, `public`, and `src/templates`. |
88
+ | **Dev Logs** | Shows real-time build times, file changes, and status codes in terminal. |
89
+
90
+ ---
91
+
92
+ ## How it works under the hood
93
+
94
+ - **Routing**: `/` → `index.html`, `/about` → `about.html`
95
+ - **HTML Compilation**: Pages are compiled with embedded live reload scripts.
96
+ - **Sass Support**: `.scss`/`.sass` files are auto-compiled to CSS in `dist/css`.
97
+ - **Server-Sent Events (SSE)**: Used for real-time updates without full page refresh.
98
+
99
+ > No extra tooling needed — everything is built-in!
100
+
101
+ ---
102
+
103
+ ## Example usage
104
+
105
+ 1. Create a new file at `src/pages/about.html`:
106
+ ```html
107
+ <!DOCTYPE html>
108
+ <html lang="en">
109
+ <head><title>About</title></head>
110
+ <body><h1>Welcome to Coralite!</h1></body>
111
+ </html>
112
+ ```
113
+
114
+ 2. Visit `http://localhost:3000/about` in your browser — it loads instantly.
115
+
116
+ 3. Edit the file → see auto-reload!
117
+
118
+ 4. Add a new SCSS file at `src/scss/style.scss`, and import it into an HTML page via `<link rel="stylesheet" href="/css/style.css">`.
119
+
120
+ ---
121
+
122
+ > **Feedback?** Found a bug or want a feature? Open an issue!
123
+
124
+ Happy building with Coralite!
@@ -0,0 +1,185 @@
1
+ #!/usr/bin/env -S node --experimental-vm-modules --experimental-import-meta-resolve
2
+
3
+ import express from 'express'
4
+ import colours from 'kleur'
5
+ import localAccess from 'local-access'
6
+ import chokidar from 'chokidar'
7
+ import loadConfig from '../src/load-config.js'
8
+ import html from '../src/build-html.js'
9
+ import buildSass from '../src/build-sass.js'
10
+ import { toCode, toMS, toTime } from '../src/build-utils.js'
11
+ import { extname, join } from 'path'
12
+ import { readFile, access, constants } from 'fs/promises'
13
+
14
+ const config = await loadConfig()
15
+ const app = express()
16
+ const port = config.server?.port || 3000
17
+ // track active connections.
18
+ const clients = new Set()
19
+
20
+ const buildHTML = await html(config)
21
+
22
+ const watchPath = [
23
+ config.public,
24
+ config.pages,
25
+ config.templates
26
+ ]
27
+
28
+ // middleware to log request information including response time and status code
29
+ app.use(function (req, res, next){
30
+ const start = process.hrtime()
31
+
32
+ // when the response is finished, calculate duration and log details
33
+ res.on('finish', function (){
34
+ const dash = colours.gray(' ─ ')
35
+ const duration = process.hrtime(start)
36
+ const uri = req.originalUrl || req.url
37
+
38
+ // log the response time and status code
39
+ process.stdout.write(toTime() + toCode(res.statusCode) + dash + toMS(duration) + dash + uri + '\n')
40
+ })
41
+ next()
42
+ })
43
+
44
+ // check if Sass is configured and add its input directory to watchPath for file changes.
45
+ if (config.sass && config.sass.input) {
46
+ watchPath.push(config.sass.input)
47
+
48
+ app.use('/css', express.static(join(config.output, 'css'), {
49
+ cacheControl: false
50
+ }))
51
+ }
52
+
53
+ app
54
+ .use(express.static(config.public, {
55
+ cacheControl: false
56
+ }))
57
+ .get('/_/rebuild', (req, res) => {
58
+ // set headers for SSE
59
+ res.writeHead(200, {
60
+ 'Content-Type': 'text/event-stream',
61
+ 'Cache-Control': 'no-cache',
62
+ Connection: 'keep-alive'
63
+ })
64
+
65
+ // add client to tracking set
66
+ clients.add(res)
67
+
68
+ // send initial connection message
69
+ res.write('data: connected\n\n')
70
+
71
+ // clean up on client disconnect
72
+ req.on('close', () => {
73
+ clients.delete(res)
74
+ res.end()
75
+ })
76
+ })
77
+ .get(/(.*)/, async (req, res) => {
78
+ // extract the requested path and its extension.
79
+ let path = req.path
80
+ const extension = extname(path)
81
+
82
+ // if no extension is present, assume it's a HTML file and append '.html'.
83
+ if (!extension) {
84
+ if ('/' === path) {
85
+ path = 'index.html'
86
+ } else if (path.endsWith('/')) {
87
+ path = path.slice(0, path.length -1) + '.html'
88
+ } else {
89
+ path += '.html'
90
+ }
91
+ }
92
+
93
+ try {
94
+ // first attempt to read the file directly.
95
+ await access(path)
96
+ const data = await readFile(path, 'utf8')
97
+
98
+ res.send(data)
99
+ } catch {
100
+ try {
101
+ // if that fails, try reading from pages directory.
102
+ const filePath = join(config.pages, path)
103
+
104
+ // check if page source file exists and is readable
105
+ await access(filePath, constants.R_OK)
106
+ const start = process.hrtime()
107
+ let duration, dash = colours.gray(' ─ ')
108
+
109
+ // build the HTML for this page using the built-in compiler.
110
+ await buildHTML.compile(filePath)
111
+ const data = await readFile(filePath, 'utf8')
112
+ // inject a script to enable live reload via Server-Sent Events
113
+ const injectedHtml = data.replace(/<\/body>/i, `
114
+ <script>
115
+ const eventSource = new EventSource('/_/rebuild');
116
+ eventSource.onmessage = function(event) {
117
+ if (event.data === 'connected') return;
118
+ // Reload page when file changes
119
+ location.reload()
120
+ }
121
+ </script>
122
+ </body>`)
123
+
124
+ // prints time and path to the file that has been changed or added.
125
+ duration = process.hrtime(start)
126
+ process.stdout.write(toTime() + colours.bgGreen('Compiled HTML') + dash + toMS(duration) + dash + path + '\n')
127
+ res.send(injectedHtml)
128
+ } catch(error) {
129
+ // if all attempts fail, respond with a 404.
130
+ res.sendStatus(404)
131
+ }
132
+ }
133
+ })
134
+
135
+ // watch for file changes
136
+ const watcher = chokidar.watch(watchPath, {
137
+ persistent: true
138
+ })
139
+
140
+ watcher
141
+ .on('change', async (path) => {
142
+ if (path.endsWith('.scss') || path.endsWith('.sass')) {
143
+ const start = process.hrtime()
144
+ let duration, dash = colours.gray(' ─ ')
145
+ // rebuild CSS and send notification
146
+ await buildSass({
147
+ ...config.sass,
148
+ output: join(config.output, 'css')
149
+ })
150
+
151
+ // prints time and path to the file that has been changed or added.
152
+ duration = process.hrtime(start)
153
+ process.stdout.write(toTime() + colours.bgGreen('Compiled SASS') + dash + toMS(duration) + dash + path + '\n')
154
+ }
155
+
156
+ clients.forEach(client => {
157
+ client.write(`data: reload\n\n`)
158
+ })
159
+ })
160
+ .on('add', async (path) => {
161
+ if (path.endsWith('.scss') || path.endsWith('.sass')) {
162
+ const start = process.hrtime()
163
+ let duration, dash = colours.gray(' ─ ')
164
+ // rebuild CSS and send notification
165
+ await buildSass({
166
+ ...config.sass,
167
+ output: join(config.output, 'css')
168
+ })
169
+
170
+ // prints time and path to the file that has been changed or added.
171
+ duration = process.hrtime(start)
172
+ process.stdout.write(toTime() + colours.bgGreen('Compiled SASS') + dash + toMS(duration) + dash + path + '\n')
173
+ }
174
+ })
175
+
176
+ app.listen(port, () => {
177
+ // @ts-ignore
178
+ const { local } = localAccess({ port })
179
+ const PAD = ' '
180
+ const border = '─'.repeat(Math.min(process.stdout.columns, 36) / 2)
181
+ // print server status
182
+ process.stdout.write('\n' + PAD + colours.green('Coralite is ready! 🚀\n\n'))
183
+ process.stdout.write(PAD + `${colours.bold('- Local:')} ${local}\n\n`)
184
+ process.stdout.write(border + colours.inverse(' LOGS ') + border + '\n\n')
185
+ })
package/package.json ADDED
@@ -0,0 +1,51 @@
1
+ {
2
+ "name": "coralite-scripts",
3
+ "version": "0.0.1",
4
+ "description": "Configuration and scripts for Create Coralite.",
5
+ "type": "module",
6
+ "bin": {
7
+ "coralite-run": "./bin/coralite-scripts.js"
8
+ },
9
+ "keywords": [
10
+ "coralite",
11
+ "static-site-generator",
12
+ "ssg",
13
+ "configuration",
14
+ "scripts"
15
+ ],
16
+ "homepage": "https://coralite.io/docs/create-scripts",
17
+ "author": {
18
+ "name": "Thomas David",
19
+ "url": "https://thomasjackdavid.com"
20
+ },
21
+ "repository": {
22
+ "type": "git",
23
+ "url": "https://codeberg.org/tjdavid/coralite.git",
24
+ "directory": "packages/coralite-scripts"
25
+ },
26
+ "bugs": {
27
+ "url": "https://codeberg.org/tjdavid/coralite/issues"
28
+ },
29
+ "imports": {
30
+ "#types": "./types/index.js"
31
+ },
32
+ "exports": {
33
+ ".": {
34
+ "default": "./src/index.js",
35
+ "types": "./types/index.js"
36
+ },
37
+ "./types": {
38
+ "default": "./types/index.js"
39
+ }
40
+ },
41
+ "license": "AGPL-3.0-only",
42
+ "devDependencies": {
43
+ "chokidar": "^4.0.3",
44
+ "express": "^5.1.0",
45
+ "html-validate": "^10.0.0",
46
+ "kleur": "^4.1.5",
47
+ "local-access": "^1.1.0",
48
+ "sass": "^1.91.0",
49
+ "coralite": "0.14.2"
50
+ }
51
+ }
@@ -0,0 +1,60 @@
1
+ import Coralite from 'coralite'
2
+
3
+ /**
4
+ * @import { CoralitePluginInstance } from 'coralite/types'
5
+ */
6
+
7
+ /**
8
+ * Builds HTML files from Coralite templates and pages.
9
+ *
10
+ * @param {Object} options
11
+ * @param {string} options.pages - Path to pages directory
12
+ * @param {string} options.templates - Path to templates directory
13
+ * @param {string} options.output - Output path for HTML files
14
+ * @param {string} options.output - Output path for HTML files
15
+ * @param {CoralitePluginInstance[]} [options.plugins=[]] - List of Coralite plugins.
16
+ */
17
+ export default async function html ({
18
+ pages,
19
+ templates,
20
+ output,
21
+ plugins
22
+ }) {
23
+ const coralite = new Coralite({
24
+ templates,
25
+ pages,
26
+ plugins
27
+ })
28
+ await coralite.initialise()
29
+
30
+ return {
31
+ /**
32
+ * Compiles the specified HTML pages using coralite and saves the output to the configured output path.
33
+ * @param {string | string[]} [paths] - Path to a single page or array of page paths relative to the pages directory. If omitted, compiles all pages.
34
+ */
35
+ async compile (paths) {
36
+ let relativePathPages
37
+
38
+ if (Array.isArray(paths)) {
39
+ relativePathPages = []
40
+
41
+ for (let i = 0; i < paths.length; i++) {
42
+ const path = paths[i]
43
+
44
+ relativePathPages.push(path.replace(pages + '/', ''))
45
+ }
46
+ } else if (paths) {
47
+ relativePathPages = paths.replace(pages + '/', '')
48
+ }
49
+
50
+ if (relativePathPages && relativePathPages[0] === '/') {
51
+ relativePathPages = relativePathPages.slice(1)
52
+ }
53
+
54
+ const document = await coralite.compile(relativePathPages)
55
+
56
+ await coralite.save(document, output)
57
+ }
58
+ }
59
+ }
60
+
@@ -0,0 +1,56 @@
1
+ import * as sass from 'sass'
2
+ import fs from 'fs/promises'
3
+ import path from 'path'
4
+
5
+ /**
6
+ * @import {Options} from 'sass'
7
+ */
8
+
9
+ /**
10
+ * Compiles SCSS files to CSS with source maps
11
+ * @param {Object} options
12
+ * @param {string} options.input - The directory containing SCSS files to compile
13
+ * @param {string} options.output - The output directory for compiled CSS files
14
+ * @param {Options<'async'>} [options.options] - Sass compile options
15
+ * @returns {Promise<void>} Resolves when all files are compiled
16
+ */
17
+ export default async function buildSass ({
18
+ input,
19
+ output,
20
+ options = {
21
+ sourceMap: true,
22
+ loadPaths: ['node_modules'],
23
+ silenceDeprecations: [
24
+ 'color-functions',
25
+ 'import',
26
+ 'global-builtin'
27
+ ]
28
+ }
29
+ }) {
30
+ try {
31
+ // ensure output directory exists
32
+ await fs.mkdir(output, { recursive: true })
33
+
34
+ // read all files from src/scss directory
35
+ const scssFiles = await fs.readdir(input)
36
+ const filteredScssFiles = scssFiles.filter(file => file.endsWith('.scss') && file[0] !== '_')
37
+
38
+ for (const file of filteredScssFiles) {
39
+ const filePath = path.join(input, file)
40
+ const outputFile = path.join(output, file.replace('.scss', '.css'))
41
+
42
+ const result = await sass.compileAsync(filePath, options)
43
+
44
+ // write the compiled CSS
45
+ await fs.writeFile(outputFile, result.css)
46
+
47
+ // write source map if enabled
48
+ if (result.sourceMap) {
49
+ const sourceMapPath = outputFile + '.map'
50
+ await fs.writeFile(sourceMapPath, JSON.stringify(result.sourceMap))
51
+ }
52
+ }
53
+ } catch (error) {
54
+ throw error
55
+ }
56
+ }
@@ -0,0 +1,39 @@
1
+ import colours from 'kleur'
2
+
3
+ /**
4
+ * Creates current time in format [HH:MM:SS].mmm (milliseconds), colored with ANSI colors, and formatted as bold white string for better readability of logs or console output
5
+ * @returns {string} - Formatted timestamp to be used within a log message.
6
+ */
7
+ export function toTime () {
8
+ const now = new Date()
9
+ const hours = now.getHours().toString().padStart(2, '0')
10
+ const minutes = now.getMinutes().toString().padStart(2, '0')
11
+ const seconds = now.getSeconds().toString().padStart(2, '0')
12
+ const milliseconds = now.getMilliseconds().toString().padStart(3, '0')
13
+
14
+ return '[' + colours.magenta(`${hours}:${minutes}:${seconds}.${milliseconds}`) + '] '
15
+ }
16
+
17
+ /**
18
+ * Creates a formatted timestamp in milliseconds with ANSI colors and bold white string for better readability of logs or console output.
19
+ * @param {[number, number]} hrtime - High Resolution Time in microseconds since epoch, used to calculate time difference between two points of execution or the start/stopwatch function call respectively.
20
+ */
21
+ export function toMS (hrtime) {
22
+ return colours.white().bold(`${(hrtime[1] / 1e6).toFixed(2)}ms`)
23
+ }
24
+
25
+ /**
26
+ * Converts HTTP status code into a colourised text.
27
+ * @param {number} code - HTTP status code to convert into a colourised text.
28
+ */
29
+ export function toCode (code) {
30
+ let fn = 'green'
31
+
32
+ if (code >= 400) {
33
+ fn = 'red'
34
+ } else if (code > 300) {
35
+ fn = 'yellow'
36
+ }
37
+
38
+ return colours[fn](code)
39
+ }
package/src/config.js ADDED
@@ -0,0 +1,50 @@
1
+ /**
2
+ * @import {CoraliteScriptConfig} from '#types'
3
+ */
4
+
5
+ /**
6
+ * @param {CoraliteScriptConfig} options
7
+ */
8
+ export function defineConfig (options) {
9
+ // Validate required properties for CoraliteConfig
10
+ if (!options || typeof options !== 'object') {
11
+ throw new Error('Configuration must be a valid object')
12
+ }
13
+
14
+ if (!options.output || typeof options.output !== 'string') {
15
+ throw new Error('Configuration must contain a valid "output" property')
16
+ }
17
+
18
+ if (!options.templates || typeof options.templates !== 'string') {
19
+ throw new Error('Configuration must contain a valid "templates" property')
20
+ }
21
+
22
+ if (!options.pages || typeof options.pages !== 'string') {
23
+ throw new Error('Configuration must contain a valid "pages" property')
24
+ }
25
+
26
+ // Validate optional server configuration
27
+ if (options.server && typeof options.server !== 'object') {
28
+ throw new Error('Configuration "server" must be an object')
29
+ }
30
+
31
+ if (options.server?.port && (typeof options.server.port !== 'number' || options.server.port <= 0)) {
32
+ throw new Error('Configuration "server.port" must be a positive number')
33
+ }
34
+
35
+ // Validate sass configuration
36
+ if (options.sass && typeof options.sass !== 'object') {
37
+ throw new Error('Configuration "sass" must be an object')
38
+ }
39
+
40
+ if (options.sass?.input && typeof options.sass.input !== 'string') {
41
+ throw new Error('Configuration "sass.input" must be a string')
42
+ }
43
+
44
+ // Validate assets path
45
+ if (!options.public || typeof options.public !== 'string') {
46
+ throw new Error('Configuration must contain a valid "public" property')
47
+ }
48
+
49
+ return options
50
+ }
package/src/index.js ADDED
@@ -0,0 +1 @@
1
+ export * from './config.js'
@@ -0,0 +1,27 @@
1
+ import { resolve } from 'path'
2
+
3
+ /**
4
+ * @import {CoraliteScriptConfig} from '#types'
5
+ */
6
+
7
+ /**
8
+ * Loads the configuration for the Coralite project.
9
+ *
10
+ * @returns {Promise<CoraliteScriptConfig>} The configuration object containing path settings or an empty promise if no config found
11
+ *
12
+ * @example
13
+ * ```js
14
+ * import loadConfig from './loadConfig.js'
15
+ *
16
+ * const config = await loadConfig()
17
+ * ```
18
+ */
19
+ export default async function loadConfig () {
20
+ try {
21
+ const config = await import(resolve('coralite.config.js'))
22
+
23
+ return config.default
24
+ } catch(error) {
25
+ throw error
26
+ }
27
+ }
package/types/index.js ADDED
@@ -0,0 +1,18 @@
1
+ /**
2
+ * @import {CoraliteConfig} from 'coralite/types'
3
+ * @import {Options} from 'sass'
4
+ */
5
+
6
+ /**
7
+ * @typedef {Object} CoraliteScriptBaseConfig
8
+ * @property {string} public - The path to the directory containing static assets.
9
+ * @property {Object} [server] - Server configuration options.
10
+ * @property {number} server.port - The port number on which the development server will run.
11
+ * @property {Object} [sass] - Sass compilation configuration.
12
+ * @property {string} sass.input - The path to the input Sass file or directory.
13
+ * @property {Options<'async'>} [sass.options] - Additional options passed to the Sass compiler.
14
+ */
15
+
16
+ /**
17
+ * @typedef {CoraliteScriptBaseConfig & CoraliteConfig} CoraliteScriptConfig
18
+ */