coralite-scripts 0.22.1 → 0.24.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/bin/index.js CHANGED
@@ -1,4 +1,4 @@
1
- #!/usr/bin/env node
1
+ #!/usr/bin/env -S node --experimental-vm-modules --experimental-import-meta-resolve
2
2
 
3
3
  import loadConfig from '../libs/load-config.js'
4
4
  import { Command, Argument } from 'commander'
@@ -34,6 +34,10 @@ const options = program.opts()
34
34
  const mode = program.args[0]
35
35
  const config = await loadConfig()
36
36
 
37
+ if (!config) {
38
+ process.exit(1)
39
+ }
40
+
37
41
  if (mode === 'dev') {
38
42
  await server(config, options)
39
43
  } else if (mode === 'build') {
@@ -23,6 +23,10 @@ export type CoraliteScriptBaseConfig = {
23
23
  * - Postcss plugins.
24
24
  */
25
25
  cssPlugins?: import("postcss").AcceptedPlugin[];
26
+ /**
27
+ * - Set build mode for the coralite instance.
28
+ */
29
+ mode?: "production" | "development";
26
30
  };
27
31
  export type CoraliteScriptConfig = CoraliteScriptBaseConfig & CoraliteConfig;
28
32
  export type CoraliteScriptOptions = {
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../types/index.js"],"names":[],"mappings":";;;;;;YAOc,MAAM;;;;aAEjB;QAA0B,IAAI,EAAnB,MAAM;KACjB;aACA;QAA2C,IAAI,EAApC,KAAK,GAAG,MAAM,GAAG,MAAM;QACR,KAAK,EAApB,MAAM;KACjB;;;;kBAAW,QAAQ,OAAO,CAAC;;;;iBAChB,OAAO,SAAS,EAAE,cAAc,EAAE;;mCAInC,wBAAwB,GAAG,cAAc;;;;;UAKxC,OAAO;;;;YACP,OAAO;;;;cACP,OAAO;;6BAvBK,MAAM;oCADC,gBAAgB"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../types/index.js"],"names":[],"mappings":";;;;;;YAOc,MAAM;;;;aAEjB;QAA0B,IAAI,EAAnB,MAAM;KACjB;aACA;QAA2C,IAAI,EAApC,KAAK,GAAG,MAAM,GAAG,MAAM;QACR,KAAK,EAApB,MAAM;KACjB;;;;kBAAW,QAAQ,OAAO,CAAC;;;;iBAChB,OAAO,SAAS,EAAE,cAAc,EAAE;;;;WAClC,YAAY,GAAG,aAAa;;mCAI7B,wBAAwB,GAAG,cAAc;;;;;UAKxC,OAAO;;;;YACP,OAAO;;;;cACP,OAAO;;6BAxBK,MAAM;oCADC,gBAAgB"}
@@ -1,6 +1,8 @@
1
1
  import { join } from 'path'
2
2
  import { access } from 'fs/promises'
3
3
  import { pathToFileURL } from 'url'
4
+ import { displayError } from './build-utils.js'
5
+ import { defineConfig } from './config.js'
4
6
 
5
7
  /**
6
8
  * @import {CoraliteScriptConfig} from '../types/index.js'
@@ -9,7 +11,7 @@ import { pathToFileURL } from 'url'
9
11
  /**
10
12
  * Loads the configuration for the Coralite project.
11
13
  *
12
- * @returns {Promise<CoraliteScriptConfig>} The configuration object containing path settings or an empty promise if no config found
14
+ * @returns {Promise<CoraliteScriptConfig|null>} The configuration object containing path settings or null if no config found or invalid
13
15
  *
14
16
  * @example
15
17
  * ```js
@@ -23,18 +25,33 @@ async function loadConfig () {
23
25
 
24
26
  try {
25
27
  await access(configPath)
28
+ } catch (error) {
29
+ if (error.code === 'ENOENT') {
30
+ displayError('Configuration file not found', `Could not find coralite.config.js at ${configPath}`)
31
+ return null
32
+ }
33
+ displayError('Failed to access configuration file', error)
34
+ return null
35
+ }
26
36
 
37
+ try {
27
38
  const config = await import(configPath.toString())
28
39
 
29
- if (config.default) {
30
- return config.default
40
+ if (!config.default) {
41
+ displayError('Config file must export a default object')
42
+ return null
43
+ }
44
+
45
+ try {
46
+ return defineConfig(config.default)
47
+ } catch (err) {
48
+ displayError('Invalid configuration', err.message)
49
+ return null
31
50
  }
32
51
  } catch (error) {
33
- console.error('Failed to load configuration file:', configPath)
34
- console.error(error)
52
+ displayError('Failed to load configuration file', error)
53
+ return null
35
54
  }
36
-
37
- return null
38
55
  }
39
56
 
40
57
  export default loadConfig
package/libs/server.js CHANGED
@@ -36,7 +36,8 @@ async function server (config, options) {
36
36
  const coralite = new Coralite({
37
37
  templates: config.templates,
38
38
  pages: config.pages,
39
- plugins: config.plugins
39
+ plugins: config.plugins,
40
+ mode: 'development'
40
41
  })
41
42
  await coralite.initialise()
42
43
  displaySuccess('Coralite initialized successfully')
@@ -156,21 +157,83 @@ async function server (config, options) {
156
157
  })
157
158
  .get(/(.*)/, async (req, res) => {
158
159
  // extract the requested path and its extension.
159
- let path = req.path
160
- const extension = extname(path)
161
-
162
- // if no extension is present, assume it's a HTML file and append '.html'.
163
- if (!extension) {
164
- if ('/' === path) {
165
- path = 'index.html'
166
- } else if (path.endsWith('/')) {
167
- path = path.slice(0, path.length - 1) + '.html'
160
+ const reqPath = req.path
161
+ const extension = extname(reqPath)
162
+
163
+ // Only handle HTML requests or extension-less requests (assumed to be pages)
164
+ if (extension && extension !== '.html') {
165
+ return res.sendStatus(404)
166
+ }
167
+
168
+ const resolveSource = async () => {
169
+ const candidates = []
170
+
171
+ // Ensure relative path doesn't start with / for joining
172
+ const relPath = reqPath.startsWith('/') ? reqPath.slice(1) : reqPath
173
+
174
+ if (reqPath.endsWith('/')) {
175
+ const key = join(relPath, 'index.html')
176
+ candidates.push({
177
+ path: join(config.pages, key),
178
+ key
179
+ })
180
+ } else if (extension === '.html') {
181
+ const key = relPath
182
+ candidates.push({
183
+ path: join(config.pages, key),
184
+ key
185
+ })
168
186
  } else {
169
- path += '.html'
187
+ // No extension, no trailing slash
188
+ const key1 = relPath + '.html'
189
+ candidates.push({
190
+ path: join(config.pages, key1),
191
+ key: key1
192
+ })
193
+
194
+ const key2 = join(relPath, 'index.html')
195
+ candidates.push({
196
+ path: join(config.pages, key2),
197
+ key: key2
198
+ })
170
199
  }
200
+
201
+ for (const candidate of candidates) {
202
+ try {
203
+ await access(candidate.path, constants.R_OK)
204
+ // Normalize key for consistency (use forward slashes)
205
+ const normalizedKey = candidate.key.split(sep).join('/')
206
+ return {
207
+ pathname: candidate.path,
208
+ key: normalizedKey
209
+ }
210
+ } catch {
211
+ // continue
212
+ }
213
+ }
214
+
215
+ // Fallback check memoryPageSource
216
+ for (const candidate of candidates) {
217
+ const normalizedKey = candidate.key.split(sep).join('/')
218
+ if (memoryPageSource.has(normalizedKey)) {
219
+ return {
220
+ pathname: memoryPageSource.get(normalizedKey),
221
+ key: normalizedKey
222
+ }
223
+ }
224
+ }
225
+
226
+ return null
171
227
  }
172
228
 
173
- const cacheKey = path.startsWith('/') ? path.slice(1) : path
229
+ const result = await resolveSource()
230
+
231
+ if (!result) {
232
+ res.sendStatus(404)
233
+ return
234
+ }
235
+
236
+ const { pathname, key: cacheKey } = result
174
237
 
175
238
  if (pageCache.has(cacheKey)) {
176
239
  res.send(pageCache.get(cacheKey))
@@ -178,99 +241,70 @@ async function server (config, options) {
178
241
  }
179
242
 
180
243
  try {
181
- // first attempt to read the file directly.
182
- await access(path)
183
- const data = await readFile(path, 'utf8')
184
-
185
- res.send(data)
186
- } catch {
187
- if (!path.endsWith('.html')) {
188
- res.sendStatus(404)
189
- } else {
190
- let pathname = join(config.pages, path)
191
-
192
- try {
193
- // if that fails, try reading from pages directory.
244
+ const start = process.hrtime()
245
+ let duration, dash = colours.gray(' ─ ')
246
+
247
+ let rebuildScript = '\n<script>\n'
248
+ rebuildScript += " const eventSource = new EventSource('/_/rebuild');\n"
249
+ rebuildScript += ' eventSource.onmessage = function(event) {\n'
250
+ rebuildScript += " if (event.data === 'connected') return;\n"
251
+ rebuildScript += ' // Reload page when file changes\n'
252
+ rebuildScript += ' location.reload()\n'
253
+ rebuildScript += ' }\n'
254
+ rebuildScript += ' </script>\n'
255
+ rebuildScript += '</body>\n'
256
+
257
+ await coralite.pages.setItem(pathname)
258
+ // build the HTML for this page using the built-in compiler.
259
+ const documents = await coralite.build(pathname, (result) => {
260
+ // inject a script to enable live reload via Server-Sent Events
261
+ const injectedHtml = result.html.replace(/<\/body>/i, rebuildScript)
262
+
263
+ const relPath = relative(config.pages, result.path.pathname)
264
+ const normalizedKey = relPath.split(sep).join('/')
265
+
266
+ // map in memory page to source
267
+ if (normalizedKey !== pathname) {
268
+ memoryPageSource.set(normalizedKey, pathname)
269
+ }
194
270
 
195
- // check if page source file exists and is readable
196
- await access(pathname, constants.R_OK)
197
- } catch {
198
- // check if it is a known in memory page source
199
- const cacheKey = path.startsWith('/') ? path.slice(1) : path
200
- if (memoryPageSource.has(cacheKey)) {
201
- pathname = memoryPageSource.get(cacheKey)
202
- } else {
203
- res.sendStatus(404)
204
- return
205
- }
271
+ // only cache pages that were out of scope of the initial page request
272
+ if (normalizedKey !== cacheKey) {
273
+ pageCache.set(normalizedKey, injectedHtml)
206
274
  }
207
275
 
208
- try {
209
- const start = process.hrtime()
210
- let duration, dash = colours.gray(' ─ ')
211
-
212
- let rebuildScript = '\n<script>\n'
213
- rebuildScript += " const eventSource = new EventSource('/_/rebuild');\n"
214
- rebuildScript += ' eventSource.onmessage = function(event) {\n'
215
- rebuildScript += " if (event.data === 'connected') return;\n"
216
- rebuildScript += ' // Reload page when file changes\n'
217
- rebuildScript += ' location.reload()\n'
218
- rebuildScript += ' }\n'
219
- rebuildScript += ' </script>\n'
220
- rebuildScript += '</body>\n'
221
-
222
- await coralite.pages.setItem(pathname)
223
- // build the HTML for this page using the built-in compiler.
224
- const documents = await coralite.build(pathname, (result) => {
225
- // inject a script to enable live reload via Server-Sent Events
226
- const injectedHtml = result.html.replace(/<\/body>/i, rebuildScript)
227
-
228
- const relPath = relative(config.pages, result.path.pathname)
229
- const normalizedKey = relPath.split(sep).join('/')
230
-
231
- // map in memory page to source
232
- if (normalizedKey !== pathname) {
233
- memoryPageSource.set(normalizedKey, pathname)
234
- }
235
-
236
- // only cache pages that were out of scope of the initial page request
237
- if (normalizedKey !== cacheKey) {
238
- pageCache.set(normalizedKey, injectedHtml)
239
- }
240
-
241
- return {
242
- path: result.path,
243
- html: injectedHtml,
244
- duration: result.duration
245
- }
246
- })
247
-
248
- // prints time and path to the file that has been changed or added.
249
- duration = process.hrtime(start)
250
- process.stdout.write(toTime() + colours.bgGreen(' Compiled HTML ') + dash + toMS(duration) + dash + path + '\n')
251
-
252
- // find the document that matches the request path
253
- const doc = documents.find(doc => {
254
- const relPath = relative(config.pages, doc.path.pathname)
255
- const normalizedKey = relPath.split(sep).join('/')
256
- return normalizedKey === cacheKey
257
- })
258
-
259
- if (doc) {
260
- res.send(doc.html)
261
- } else {
262
- res.sendStatus(404)
263
- }
264
- } catch (error) {
265
- // If headers haven't been sent, send 500
266
- if (!res.headersSent) {
267
- res.status(500).send(error.message)
268
- }
269
- displayError('Request processing failed', error)
276
+ return {
277
+ path: result.path,
278
+ html: injectedHtml,
279
+ duration: result.duration
270
280
  }
281
+ })
282
+
283
+ // prints time and path to the file that has been changed or added.
284
+ duration = process.hrtime(start)
285
+ process.stdout.write(toTime() + colours.bgGreen(' Compiled HTML ') + dash + toMS(duration) + dash + '/' + cacheKey + '\n')
286
+
287
+ // find the document that matches the request path
288
+ const doc = documents.find(doc => {
289
+ const relPath = relative(config.pages, doc.path.pathname)
290
+ const normalizedKey = relPath.split(sep).join('/')
291
+ return normalizedKey === cacheKey
292
+ })
293
+
294
+ if (doc) {
295
+ res.send(doc.html)
296
+ } else {
297
+ res.sendStatus(404)
298
+ }
299
+ } catch (error) {
300
+ // If headers haven't been sent, send 500
301
+ if (!res.headersSent) {
302
+ res.status(500).send(error.message)
271
303
  }
304
+ displayError('Request processing failed', error)
272
305
  }
273
- })
306
+ }
307
+ )
274
308
 
275
309
  // watch for file changes
276
310
  const watcher = chokidar.watch(watchPath, {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-scripts",
3
- "version": "0.22.1",
3
+ "version": "0.24.0",
4
4
  "description": "Configuration and scripts for Create Coralite.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -50,6 +50,7 @@
50
50
  "dependencies": {
51
51
  "chokidar": "^4.0.3",
52
52
  "commander": "^14.0.0",
53
+ "cross-env": "^10.1.0",
53
54
  "express": "^5.1.0",
54
55
  "kleur": "^4.1.5",
55
56
  "local-access": "^1.1.0",
@@ -57,7 +58,7 @@
57
58
  "portfinder": "^1.0.38",
58
59
  "postcss": "^8.5.6",
59
60
  "sass": "^1.91.0",
60
- "coralite": "0.22.1"
61
+ "coralite": "0.24.0"
61
62
  },
62
63
  "scripts": {
63
64
  "build": "premove dist && pnpm build-types",
package/types/index.js CHANGED
@@ -13,6 +13,7 @@
13
13
  * @property {string} styles.input - The path to the main stylesheet file to process.
14
14
  * @property {Options<'async'>} [sassOptions] - Additional options passed to the Sass compiler.
15
15
  * @property {import('postcss').AcceptedPlugin[]} [cssPlugins] - Postcss plugins.
16
+ * @property {'production' | 'development'} [mode='production'] - Set build mode for the coralite instance.
16
17
  */
17
18
 
18
19
  /**
@@ -1,30 +0,0 @@
1
- import Coralite from 'coralite'
2
-
3
- /**
4
- * @import {CoraliteScriptConfig} from '../types/index.js'
5
- * @import {CoraliteResult} from 'coralite/types'
6
- */
7
-
8
- /**
9
- * @param {CoraliteScriptConfig} config
10
- * @returns {Promise<CoraliteResult[]>}
11
- */
12
- async function buildHTML (config) {
13
- // start coralite
14
- const coralite = new Coralite({
15
- templates: config.templates,
16
- pages: config.pages,
17
- plugins: config.plugins
18
- })
19
- await coralite.initialise()
20
-
21
- // compile website
22
- const documents = await coralite.compile()
23
-
24
- // save documents
25
- await coralite.save(documents, config.output)
26
-
27
- return documents
28
- }
29
-
30
- export default buildHTML