coralite-scripts 0.36.2 → 0.37.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
@@ -8,7 +8,7 @@ import pkg from '../package.json' with { type: 'json' }
8
8
  import buildStyles from '../libs/build-styles.js'
9
9
  import { join, relative, dirname } from 'node:path'
10
10
  import { deleteDirectoryRecursive, copyDirectory, toMS, toTime, displayError, displayWarning, displayInfo } from '../libs/build-utils.js'
11
- import { Coralite } from 'coralite'
11
+ import { createCoralite } from 'coralite'
12
12
  import { mkdir, writeFile } from 'node:fs/promises'
13
13
  import ora from 'ora'
14
14
 
@@ -58,10 +58,8 @@ if (mode === 'dev') {
58
58
  // delete old output files
59
59
  deleteDirectoryRecursive(config.output)
60
60
 
61
- const start = process.hrtime()
62
-
63
61
  // start coralite
64
- const coralite = new Coralite({
62
+ const coralite = await createCoralite({
65
63
  components: config.components,
66
64
  pages: config.pages,
67
65
  plugins: config.plugins,
@@ -83,10 +81,10 @@ if (mode === 'dev') {
83
81
  }
84
82
  }
85
83
  })
86
- await coralite.initialise()
87
84
 
88
85
  let spinner
89
86
  let pageCount = 0
87
+ let skippedCount = 0
90
88
 
91
89
  try {
92
90
  let componentCount = 0
@@ -95,8 +93,24 @@ if (mode === 'dev') {
95
93
  spinner = ora('Building pages...').start()
96
94
  }
97
95
 
96
+ const updateSpinnerText = () => {
97
+ if (skippedCount > 0) {
98
+ spinner.text = `Building pages... (${pageCount} completed, ${skippedCount} skipped)`
99
+ } else {
100
+ spinner.text = `Building pages... (${pageCount} completed)`
101
+ }
102
+ }
103
+
98
104
  // compile website
99
105
  await coralite.build(async (result) => {
106
+ if (result.status === 'skipped') {
107
+ skippedCount++
108
+ if (!options.verbose) {
109
+ updateSpinnerText()
110
+ }
111
+ return
112
+ }
113
+
100
114
  const relativeDir = relative(config.pages, result.path.dirname)
101
115
  const outDir = join(config.output, relativeDir)
102
116
  const outFile = join(outDir, result.path.filename)
@@ -108,7 +122,7 @@ if (mode === 'dev') {
108
122
  process.stdout.write(toTime() + toMS(result.duration) + dash + result.path.pathname + '\n')
109
123
  } else {
110
124
  pageCount++
111
- spinner.text = `Building pages... (${pageCount} completed)`
125
+ updateSpinnerText()
112
126
  }
113
127
  })
114
128
 
@@ -130,7 +144,11 @@ if (mode === 'dev') {
130
144
  }
131
145
 
132
146
  if (!options.verbose) {
133
- spinner.succeed(`Pages built (${pageCount} completed)`)
147
+ if (skippedCount > 0) {
148
+ spinner.succeed(`Pages built (${pageCount} completed, ${skippedCount} skipped)`)
149
+ } else {
150
+ spinner.succeed(`Pages built (${pageCount} completed)`)
151
+ }
134
152
  if (componentCount > 0) {
135
153
  ora(`Components built (${componentCount} completed)`).succeed()
136
154
  }
@@ -1 +1 @@
1
- {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../libs/config.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,sCAzBW,oBAAoB,GAClB,oBAAoB,CAmJhC;0CA7JsC,mBAAmB"}
1
+ {"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../libs/config.js"],"names":[],"mappings":"AAAA;;GAEG;AAEH;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA8BG;AACH,sCAzBW,oBAAoB,GAClB,oBAAoB,CAoJhC;0CA9JsC,mBAAmB"}
@@ -11,6 +11,9 @@ export type CoraliteScriptBaseConfig = {
11
11
  server?: {
12
12
  port: number;
13
13
  };
14
+ /**
15
+ * - The configuration options for style processing.
16
+ */
14
17
  styles?: {
15
18
  input?: string[];
16
19
  processors?: {
@@ -41,4 +44,5 @@ export type CoraliteScriptOptions = {
41
44
  verbose?: boolean;
42
45
  };
43
46
  import type { Options } from 'sass';
47
+ import type { CoraliteConfig } from 'coralite/types';
44
48
  //# sourceMappingURL=index.d.ts.map
@@ -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;QAA6B,KAAK,GAAvB,MAAM,EAAE;QACQ,UAAU,GACrC;YAAgD,IAAI,GAAzC,QAAQ,OAAO,CAAC;YACW,OAAO,GAC7C;gBAA0E,OAAO,GAAtE,OAAO,SAAS,EAAE,cAAc,EAAE;aAC7C;SAAA;KAAA;;;;WAAW,YAAY,GAAG,aAAa;;mCAI7B,wBAAwB,iBAAiB;;;;;UAKxC,OAAO;;;;YACP,OAAO;;;;cACP,OAAO;;6BAzBK,MAAM"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../types/index.js"],"names":[],"mappings":";;;;;;YAOc,MAAM;;;;aAEjB;QAA0B,IAAI,EAAnB,MAAM;KACjB;;;;aACA;QAA6B,KAAK,GAAvB,MAAM,EAAE;QACQ,UAAU,GACrC;YAAgD,IAAI,GAAzC,QAAQ,OAAO,CAAC;YACW,OAAO,GAC7C;gBAA0E,OAAO,GAAtE,OAAO,SAAS,EAAE,cAAc,EAAE;aAC7C;SAAA;KAAA;;;;WAAW,YAAY,GAAG,aAAa;;mCAI7B,wBAAwB,GAAG,cAAc;;;;;UAKxC,OAAO;;;;YACP,OAAO;;;;cACP,OAAO;;6BAzBK,MAAM;oCADC,gBAAgB"}
@@ -12,13 +12,10 @@ import path from 'path'
12
12
 
13
13
  /**
14
14
  * Compiles SCSS and CSS files with optional PostCSS processing
15
- * @param {Object} options
15
+ * @param {Object} options - The configuration options for building styles.
16
16
  * @param {string[]} options.input - Array of input file paths
17
17
  * @param {string} options.output - Output directory for compiled CSS files
18
18
  * @param {Object} [options.processors] - Processor configurations
19
- * @param {import('sass').Options<'async'>} [options.processors.scss] - Sass options
20
- * @param {Object} [options.processors.postcss] - PostCSS options
21
- * @param {import('postcss').AcceptedPlugin[]} [options.processors.postcss.plugins] - PostCSS plugins
22
19
  * @returns {Promise<BuildStylesResult[]>}
23
20
  */
24
21
  async function buildStyles ({
@@ -29,6 +26,7 @@ async function buildStyles ({
29
26
  const scssOptions = {
30
27
  sourceMap: true,
31
28
  loadPaths: ['node_modules'],
29
+ // @ts-ignore
32
30
  silenceDeprecations: [
33
31
  'color-functions',
34
32
  'import',
@@ -50,6 +48,7 @@ async function buildStyles ({
50
48
  let map
51
49
 
52
50
  if (ext === '.scss' || ext === '.sass') {
51
+ // @ts-ignore
53
52
  const result = await sass.compileAsync(filePath, scssOptions)
54
53
  css = result.css
55
54
  map = result.sourceMap
@@ -2,6 +2,7 @@ import colours from 'kleur'
2
2
  import fs from 'node:fs'
3
3
  import path from 'node:path'
4
4
  import { cp } from 'node:fs/promises'
5
+ import { fileURLToPath } from 'node:url'
5
6
 
6
7
  /**
7
8
  * 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
@@ -111,6 +112,49 @@ export function displayError (message, error) {
111
112
  }
112
113
 
113
114
  if (isCoraliteError) {
115
+ const displayFile = targetError.stackFile || targetError.filePath
116
+ if (displayFile) {
117
+ let absolutePath = displayFile
118
+ if (absolutePath.startsWith('file://')) {
119
+ try {
120
+ absolutePath = fileURLToPath(absolutePath)
121
+ } catch {
122
+ }
123
+ }
124
+
125
+ const relativePath = path.relative(process.cwd(), absolutePath)
126
+ let location = relativePath
127
+ if (targetError.line) {
128
+ location += `:${targetError.line}`
129
+ if (targetError.column) {
130
+ location += `:${targetError.column}`
131
+ }
132
+ }
133
+
134
+ process.stdout.write(colours.bold(`> ${location}: `) + colours.red('error: ') + targetError.message + '\n')
135
+
136
+ if (fs.existsSync(absolutePath) && targetError.line) {
137
+ try {
138
+ const content = fs.readFileSync(absolutePath, 'utf8')
139
+ const lines = content.split('\n')
140
+ const errorLine = lines[targetError.line - 1]
141
+ if (errorLine !== undefined) {
142
+ const gutter = ` ${targetError.line} | `
143
+ process.stdout.write(colours.grey(gutter) + errorLine + '\n')
144
+ if (targetError.column) {
145
+ // To handle tabs correctly, we take the prefix of the line up to the error column
146
+ // and replace all non-whitespace characters with spaces.
147
+ const prefix = errorLine.substring(0, targetError.column - 1)
148
+ const padding = ' '.repeat(gutter.length) + prefix.replace(/\S/g, ' ')
149
+ process.stdout.write(padding + colours.cyan('~') + '\n')
150
+ }
151
+ }
152
+ } catch {
153
+ }
154
+ }
155
+ process.stdout.write('\n')
156
+ }
157
+
114
158
  process.stdout.write(indent + colours.magenta('Component context:') + '\n')
115
159
  if (targetError.componentId) {
116
160
  process.stdout.write(indent + ' ' + colours.cyan('ID: ') + targetError.componentId + '\n')
@@ -129,15 +173,17 @@ export function displayError (message, error) {
129
173
 
130
174
  let errorDetails = ''
131
175
 
132
- if (error instanceof Error ||
133
- (error !== null
134
- && typeof error === 'object'
135
- && 'message' in error
136
- && 'stack' in error
176
+ const errorToStack = error.cause && error.cause.stack ? error.cause : error
177
+
178
+ if (errorToStack instanceof Error ||
179
+ (errorToStack !== null
180
+ && typeof errorToStack === 'object'
181
+ && 'message' in errorToStack
182
+ && 'stack' in errorToStack
137
183
  )
138
184
  ) {
139
185
  // @ts-ignore
140
- errorDetails = error.stack || error.message
186
+ errorDetails = errorToStack.stack || errorToStack.message
141
187
  } else if (typeof error === 'string') {
142
188
  errorDetails = error
143
189
  } else if (typeof error === 'object' && error !== null) {
package/libs/config.js CHANGED
@@ -91,6 +91,7 @@ export function defineConfig (options) {
91
91
  }
92
92
  }
93
93
 
94
+ // @ts-ignore
94
95
  if (options.styles.type) {
95
96
  throw new Error('Coralite Config Error: The "styles" configuration has been upgraded. "input" must now be an array, and "type" has been replaced by the "processors" object. Please update your coralite.config.js.')
96
97
  }
package/libs/server.js CHANGED
@@ -6,14 +6,119 @@ import buildStyles from './build-styles.js'
6
6
  import { displayError, displayInfo, displayWarning, displaySuccess, toCode, toMS, toTime, deleteDirectoryRecursive } from './build-utils.js'
7
7
  import { dirname, extname, join, normalize, relative, sep } from 'path'
8
8
  import { access, constants, mkdir, readFile, writeFile } from 'fs/promises'
9
- import Coralite from 'coralite'
10
- import { existsSync, mkdirSync } from 'fs'
9
+ import { createCoralite } from 'coralite'
10
+ import { existsSync } from 'fs'
11
11
  import portfinder from 'portfinder'
12
12
 
13
13
  /**
14
14
  * @import {CoraliteScriptConfig, CoraliteScriptOptions} from '../types/index.js'
15
15
  */
16
16
 
17
+ /**
18
+ * Resolves the requested path to a file or a virtual page.
19
+ *
20
+ * @param {string} reqPath - The requested URL path.
21
+ * @param {string} extension - The extension of the requested path.
22
+ * @param {CoraliteScriptConfig} config - The Coralite configuration.
23
+ * @param {any} coralite - The Coralite instance.
24
+ * @param {Map<string, string>} memoryPageSource - Map of in-memory page sources.
25
+ * @returns {Promise<{pathname: string, key: string}|null>}
26
+ */
27
+ export async function resolveSource (reqPath, extension, config, coralite, memoryPageSource) {
28
+ const candidates = []
29
+ const pagesRoot = normalize(config.pages)
30
+ const isPathInsideRoot = (rootPath, candidatePath) => {
31
+ const rel = relative(rootPath, candidatePath)
32
+ return rel !== '..' && !rel.startsWith(`..${sep}`) && rel !== '' ? true : candidatePath === rootPath
33
+ }
34
+
35
+ // Ensure relative path doesn't start with / for joining
36
+ const relPath = reqPath.startsWith('/') ? reqPath.slice(1) : reqPath
37
+
38
+ if (reqPath.endsWith('/')) {
39
+ const key = join(relPath, 'index.html')
40
+ const candidatePath = normalize(join(config.pages, key))
41
+
42
+ if (isPathInsideRoot(pagesRoot, candidatePath)) {
43
+ candidates.push({
44
+ path: candidatePath,
45
+ key
46
+ })
47
+ }
48
+ } else if (extension === '.html') {
49
+ const key = relPath
50
+ const candidatePath = normalize(join(config.pages, key))
51
+
52
+ if (isPathInsideRoot(pagesRoot, candidatePath)) {
53
+ candidates.push({
54
+ path: candidatePath,
55
+ key
56
+ })
57
+ }
58
+ } else {
59
+ // No extension, no trailing slash
60
+ const key1 = relPath + '.html'
61
+ const candidatePath1 = normalize(join(config.pages, key1))
62
+
63
+ if (isPathInsideRoot(pagesRoot, candidatePath1)) {
64
+ candidates.push({
65
+ path: candidatePath1,
66
+ key: key1
67
+ })
68
+ }
69
+
70
+ const key2 = join(relPath, 'index.html')
71
+ const candidatePath2 = normalize(join(config.pages, key1))
72
+
73
+ if (isPathInsideRoot(pagesRoot, candidatePath2)) {
74
+ candidates.push({
75
+ path: candidatePath2,
76
+ key: key2
77
+ })
78
+ }
79
+ }
80
+
81
+ for (const candidate of candidates) {
82
+ try {
83
+ await access(candidate.path, constants.R_OK)
84
+ // Normalize key for consistency (use forward slashes)
85
+ const normalizedKey = candidate.key.split(sep).join('/')
86
+ return {
87
+ pathname: candidate.path,
88
+ key: normalizedKey
89
+ }
90
+ } catch {
91
+ // continue
92
+ }
93
+ }
94
+
95
+ // Fallback check coralite pages collection (supports virtual pages)
96
+ for (const candidate of candidates) {
97
+ const item = coralite.pages.getItem(candidate.path)
98
+
99
+ if (item) {
100
+ const normalizedKey = candidate.key.split(sep).join('/')
101
+ return {
102
+ pathname: candidate.path,
103
+ key: normalizedKey
104
+ }
105
+ }
106
+ }
107
+
108
+ // Fallback check memoryPageSource
109
+ for (const candidate of candidates) {
110
+ const normalizedKey = candidate.key.split(sep).join('/')
111
+ if (memoryPageSource.has(normalizedKey)) {
112
+ return {
113
+ pathname: memoryPageSource.get(normalizedKey),
114
+ key: normalizedKey
115
+ }
116
+ }
117
+ }
118
+
119
+ return null
120
+ }
121
+
17
122
  /**
18
123
  * Starts a development server with hot-reloading capabilities
19
124
  * @param {CoraliteScriptConfig} config - Coralite configuration
@@ -53,7 +158,7 @@ async function server (config, options) {
53
158
 
54
159
  try {
55
160
  await access(configPath, constants.F_OK)
56
- } catch (err) {
161
+ } catch {
57
162
  return
58
163
  }
59
164
 
@@ -73,7 +178,7 @@ async function server (config, options) {
73
178
  }
74
179
  }
75
180
  }
76
- } catch (error) {
181
+ } catch {
77
182
  // ignore any other unexpected errors during reading/parsing
78
183
  }
79
184
  }
@@ -101,7 +206,7 @@ async function server (config, options) {
101
206
 
102
207
  pageCache.clear()
103
208
 
104
- coralite = new Coralite({
209
+ coralite = await createCoralite({
105
210
  components: currentConfig.components,
106
211
  pages: currentConfig.pages,
107
212
  plugins: currentConfig.plugins,
@@ -126,7 +231,6 @@ async function server (config, options) {
126
231
  }
127
232
  })
128
233
 
129
- await coralite.initialise()
130
234
 
131
235
  displaySuccess('Coralite initialized successfully')
132
236
 
@@ -141,6 +245,7 @@ async function server (config, options) {
141
245
 
142
246
  for (const plugin of currentConfig.plugins) {
143
247
  if (typeof plugin.server === 'function') {
248
+ // @ts-ignore
144
249
  await plugin.server(app, coralite)
145
250
  }
146
251
  }
@@ -171,9 +276,8 @@ async function server (config, options) {
171
276
 
172
277
  // middleware to log request information including response time and status code
173
278
  app.use(function (req, res, next) {
174
- const start = process.hrtime()
175
-
176
279
  if (options.verbose) {
280
+ const start = process.hrtime()
177
281
  // when the response is finished, calculate duration and log details
178
282
  res.on('finish', function () {
179
283
  const dash = colours.gray(' ─ ')
@@ -210,14 +314,11 @@ async function server (config, options) {
210
314
  cacheControl: false
211
315
  }))
212
316
 
213
- const start = process.hrtime()
214
-
215
317
  // rebuild CSS and send notification
216
318
  const results = await buildStyles({
217
319
  input: config.styles.input,
218
320
  output: join(config.output, 'assets', 'css'),
219
- processors: config.styles.processors,
220
- start
321
+ processors: config.styles.processors
221
322
  })
222
323
 
223
324
  const dash = colours.gray(' ─ ')
@@ -261,68 +362,7 @@ async function server (config, options) {
261
362
  return res.sendStatus(404)
262
363
  }
263
364
 
264
- const resolveSource = async () => {
265
- const candidates = []
266
-
267
- // Ensure relative path doesn't start with / for joining
268
- const relPath = reqPath.startsWith('/') ? reqPath.slice(1) : reqPath
269
-
270
- if (reqPath.endsWith('/')) {
271
- const key = join(relPath, 'index.html')
272
- candidates.push({
273
- path: join(config.pages, key),
274
- key
275
- })
276
- } else if (extension === '.html') {
277
- const key = relPath
278
- candidates.push({
279
- path: join(config.pages, key),
280
- key
281
- })
282
- } else {
283
- // No extension, no trailing slash
284
- const key1 = relPath + '.html'
285
- candidates.push({
286
- path: join(config.pages, key1),
287
- key: key1
288
- })
289
-
290
- const key2 = join(relPath, 'index.html')
291
- candidates.push({
292
- path: join(config.pages, key2),
293
- key: key2
294
- })
295
- }
296
-
297
- for (const candidate of candidates) {
298
- try {
299
- await access(candidate.path, constants.R_OK)
300
- // Normalize key for consistency (use forward slashes)
301
- const normalizedKey = candidate.key.split(sep).join('/')
302
- return {
303
- pathname: candidate.path,
304
- key: normalizedKey
305
- }
306
- } catch {
307
- // continue
308
- }
309
- }
310
-
311
- // Fallback check memoryPageSource
312
- for (const candidate of candidates) {
313
- const normalizedKey = candidate.key.split(sep).join('/')
314
- if (memoryPageSource.has(normalizedKey)) {
315
- return {
316
- pathname: memoryPageSource.get(normalizedKey),
317
- key: normalizedKey
318
- }
319
- }
320
- }
321
-
322
- return null
323
- }
324
-
325
- const result = await resolveSource()
365
+ const result = await resolveSource(reqPath, extension, config, coralite, memoryPageSource)
326
366
 
327
367
  if (!result) {
328
368
  res.sendStatus(404)
@@ -350,7 +390,13 @@ async function server (config, options) {
350
390
  rebuildScript += ' </script>\n'
351
391
  rebuildScript += '</body>\n'
352
392
 
353
- await coralite.pages.setItem(pathname)
393
+ // Only set item if it's not already in the collection (virtual pages are pre-registered)
394
+ const item = coralite.pages.getItem(pathname)
395
+
396
+ if (!item || item.virtual !== true) {
397
+ await coralite.pages.setItem(pathname)
398
+ }
399
+
354
400
  // build the HTML for this page using the built-in compiler.
355
401
  const documents = await coralite.build(pathname, async (result) => {
356
402
  // inject a script to enable live reload via Server-Sent Events
@@ -441,6 +487,7 @@ async function server (config, options) {
441
487
  // Helper function to debounce compilations
442
488
  const debounceCompile = () => {
443
489
  if (compileTimeout) {
490
+ // @ts-ignore
444
491
  clearTimeout(compileTimeout)
445
492
  }
446
493
  compileTimeout = setTimeout(async () => {
@@ -451,7 +498,6 @@ async function server (config, options) {
451
498
  pageCache.clear()
452
499
 
453
500
  isCompiling = true
454
- const start = process.hrtime()
455
501
  let dash = colours.gray(' ─ ')
456
502
 
457
503
  // Process all pending changes
@@ -504,8 +550,7 @@ async function server (config, options) {
504
550
  const results = await buildStyles({
505
551
  input: currentConfig.styles.input,
506
552
  processors: currentConfig.styles.processors,
507
- output: join(config.output, 'assets', 'css'),
508
- start
553
+ output: join(config.output, 'assets', 'css')
509
554
  })
510
555
 
511
556
  for (const result of results) {
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "coralite-scripts",
3
- "version": "0.36.2",
3
+ "version": "0.37.0",
4
4
  "description": "Configuration and scripts for Create Coralite.",
5
5
  "type": "module",
6
6
  "bin": {
@@ -61,10 +61,11 @@
61
61
  "portfinder": "^1.0.38",
62
62
  "postcss": "^8.5.6",
63
63
  "sass": "^1.91.0",
64
- "coralite": "0.36.2"
64
+ "coralite": "0.37.0"
65
65
  },
66
66
  "scripts": {
67
67
  "build": "premove dist && pnpm build-types",
68
- "build-types": "tsc"
68
+ "build-types": "tsc",
69
+ "test-unit": "node --test tests/**/*.spec.js"
69
70
  }
70
71
  }
package/types/index.js CHANGED
@@ -8,11 +8,11 @@
8
8
  * @property {string} public - The path to the directory containing static assets.
9
9
  * @property {Object} [server] - Server configuration options.
10
10
  * @property {number} server.port - The port number on which the development server will run.
11
- * @property {Object} [styles]
11
+ * @property {Object} [styles] - The configuration options for style processing.
12
12
  * @property {string[]} [styles.input] - Array of inputs, mixing scss and css
13
- * @property {Object} [styles.processors]
13
+ * @property {Object} [styles.processors] - The configuration for style processors like Sass or PostCSS.
14
14
  * @property {Options<'async'>} [styles.processors.scss] - Native Dart Sass options
15
- * @property {Object} [styles.processors.postcss]
15
+ * @property {Object} [styles.processors.postcss] - The configuration for PostCSS.
16
16
  * @property {import('postcss').AcceptedPlugin[]} [styles.processors.postcss.plugins] - Native PostCSS plugins
17
17
  * @property {'production' | 'development'} [mode='production'] - Set build mode for the coralite instance.
18
18
  */