poops 1.1.0 → 1.2.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/lib/postcss.js ADDED
@@ -0,0 +1,127 @@
1
+ import { transform } from 'esbuild'
2
+ import fs from 'node:fs'
3
+ import {
4
+ pathExists,
5
+ mkPath,
6
+ insertMinSuffix,
7
+ buildStyleOutputFilePath,
8
+ fillBannerTemplate,
9
+ buildTime,
10
+ fileSize
11
+ } from './utils/helpers.js'
12
+ import log from './utils/log.js'
13
+
14
+ let postcss
15
+
16
+ async function loadPostCSS() {
17
+ if (postcss) return true
18
+ try {
19
+ postcss = (await import('postcss')).default
20
+ return true
21
+ } catch (err) {
22
+ log({ tag: 'postcss', error: true, text: 'Missing dependency. Install: npm i -D postcss' })
23
+ return false
24
+ }
25
+ }
26
+
27
+ async function resolvePlugins(plugins) {
28
+ if (!plugins || !plugins.length) return []
29
+
30
+ const resolved = []
31
+ for (const plugin of plugins) {
32
+ if (typeof plugin === 'string') {
33
+ try {
34
+ const mod = await import(plugin)
35
+ resolved.push((mod.default || mod)())
36
+ } catch (err) {
37
+ log({ tag: 'postcss', error: true, text: `Failed to load plugin: ${plugin}` })
38
+ throw err
39
+ }
40
+ } else if (Array.isArray(plugin)) {
41
+ const [name, options] = plugin
42
+ try {
43
+ const mod = await import(name)
44
+ resolved.push((mod.default || mod)(options))
45
+ } catch (err) {
46
+ log({ tag: 'postcss', error: true, text: `Failed to load plugin: ${name}` })
47
+ throw err
48
+ }
49
+ } else {
50
+ resolved.push(plugin)
51
+ }
52
+ }
53
+ return resolved
54
+ }
55
+
56
+ export default class PostCSS {
57
+ constructor(config) {
58
+ this.config = config
59
+ this.banner = config.banner ? fillBannerTemplate(config.banner) : null
60
+ }
61
+
62
+ async compile() {
63
+ if (!this.config.postcss) return
64
+ if (!(await loadPostCSS())) return
65
+
66
+ this.config.postcss = Array.isArray(this.config.postcss) ? this.config.postcss : [this.config.postcss]
67
+ for (const entry of this.config.postcss) {
68
+ if (entry.in && entry.out && pathExists(entry.in)) {
69
+ mkPath(entry.out)
70
+ await this.compileEntry(entry.in, entry.out, entry.options)
71
+ }
72
+ }
73
+ }
74
+
75
+ async compileEntry(infilePath, outfilePath, options = {}) {
76
+ outfilePath = buildStyleOutputFilePath(infilePath, outfilePath)
77
+
78
+ const input = fs.readFileSync(infilePath, 'utf-8')
79
+ const start = performance.now()
80
+
81
+ let result
82
+ try {
83
+ const plugins = await resolvePlugins(options.plugins)
84
+ const processor = postcss(plugins)
85
+ result = await processor.process(input, {
86
+ from: infilePath,
87
+ to: outfilePath
88
+ })
89
+ } catch (err) {
90
+ log({ tag: 'postcss', error: true, text: 'Failed compiling:', link: outfilePath })
91
+ console.error(err)
92
+ return
93
+ }
94
+
95
+ let css = result.css
96
+ if (this.banner) css = this.banner + '\n' + css
97
+ fs.writeFileSync(outfilePath, css)
98
+ const end = performance.now()
99
+ if (!options.justMinified) log({ tag: 'postcss', text: 'Compiled:', link: outfilePath, size: fileSize(outfilePath), time: buildTime(start, end) })
100
+
101
+ const minPath = insertMinSuffix(outfilePath)
102
+ if (options.minify) {
103
+ try {
104
+ const minStart = !options.justMinified ? performance.now() : start
105
+
106
+ const minified = await transform(css, {
107
+ loader: 'css',
108
+ minify: true
109
+ })
110
+
111
+ if (this.banner) minified.code = this.banner + '\n' + minified.code
112
+ fs.writeFileSync(minPath, minified.code)
113
+ const minEnd = performance.now()
114
+ log({ tag: 'postcss', text: 'Compiled:', link: minPath, size: fileSize(minPath), time: buildTime(minStart, minEnd) })
115
+ } catch (err) {
116
+ log({ tag: 'postcss', error: true, text: 'Failed compiling:', link: minPath })
117
+ console.error(err)
118
+ }
119
+
120
+ if (options.justMinified) {
121
+ fs.unlinkSync(outfilePath)
122
+ }
123
+ } else {
124
+ if (pathExists(minPath)) fs.unlinkSync(minPath)
125
+ }
126
+ }
127
+ }
@@ -4,42 +4,45 @@ import {
4
4
  pathExists,
5
5
  mkPath,
6
6
  insertMinSuffix,
7
+ pathContainsPathSegment,
7
8
  fillBannerTemplate,
8
9
  buildTime,
9
10
  fileSize
10
11
  } from './utils/helpers.js'
11
12
  import fs from 'node:fs'
12
13
  import path from 'node:path'
13
- import { pathToFileURL } from 'node:url'
14
- import PrintStyle from './utils/print-style.js'
14
+ import { createRequire } from 'node:module'
15
+ import log from './utils/log.js'
15
16
 
16
- const pstyle = new PrintStyle()
17
-
18
- export default class SSG {
17
+ export default class Reactor {
19
18
  constructor(config) {
20
19
  this.config = config
21
20
  this.rendered = {}
22
- this.banner = fillBannerTemplate(config.banner, config.pkg)
21
+ this.renderedChanged = false
22
+ this.banner = config.banner ? fillBannerTemplate(config.banner, config.pkg) : null
23
23
  }
24
24
 
25
25
  async compile() {
26
- if (!this.config.ssg) return
27
- this.config.ssg = Array.isArray(this.config.ssg) ? this.config.ssg : [this.config.ssg]
26
+ if (!this.config.reactor) return
27
+ this.config.reactor = Array.isArray(this.config.reactor) ? this.config.reactor : [this.config.reactor]
28
+ const prevRendered = this.rendered
28
29
  this.rendered = {}
29
30
 
30
- for (const entry of this.config.ssg) {
31
+ for (const entry of this.config.reactor) {
31
32
  if (entry.component && entry.inject && pathExists(entry.component)) {
32
33
  await this.compileEntry(entry)
33
34
  }
34
35
  }
36
+
37
+ this.renderedChanged = JSON.stringify(this.rendered) !== JSON.stringify(prevRendered)
35
38
  }
36
39
 
37
40
  async compileEntry(entry) {
38
41
  const { component, in: client, out, inject, options = {} } = entry
39
42
 
40
43
  // --- Step 1: Server-side render the component ---
41
- const tmpWrapper = component.replace(/(\.[^.]+)$/, '.ssg-tmp$1')
42
- const tmpBundle = component.replace(/(\.[^.]+)$/, '.ssg-bundle.cjs')
44
+ const tmpWrapper = component.replace(/(\.[^.]+)$/, `.reactor-tmp-${inject}$1`)
45
+ const tmpBundle = component.replace(/(\.[^.]+)$/, `.reactor-bundle-${inject}.cjs`)
43
46
 
44
47
  const wrapperCode = `
45
48
  import { renderToString } from 'react-dom/server';
@@ -51,7 +54,7 @@ export const html = renderToString(React.createElement(Component));
51
54
  try {
52
55
  fs.writeFileSync(tmpWrapper, wrapperCode)
53
56
 
54
- const ssgStart = performance.now()
57
+ const renderStart = performance.now()
55
58
  await build({
56
59
  logLevel: 'error',
57
60
  entryPoints: [tmpWrapper],
@@ -63,15 +66,17 @@ export const html = renderToString(React.createElement(Component));
63
66
  nodePaths: this.config.includePaths
64
67
  })
65
68
 
66
- const bundleUrl = pathToFileURL(path.resolve(tmpBundle)).href
67
- const mod = await import(`${bundleUrl}?t=${Date.now()}`)
69
+ const bundlePath = path.resolve(tmpBundle)
70
+ const require = createRequire(import.meta.url)
71
+ delete require.cache[bundlePath]
72
+ const mod = require(bundlePath)
68
73
  this.rendered[inject] = mod.html
69
- const ssgEnd = performance.now()
74
+ const renderEnd = performance.now()
70
75
 
71
- console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.dim}Rendered:${pstyle.reset} ${pstyle.italic + pstyle.underline}${component}${pstyle.reset} ${pstyle.bold}${inject}${pstyle.reset} ${pstyle.green}(${buildTime(ssgStart, ssgEnd)})${pstyle.reset}`)
76
+ log({ tag: 'reactor', text: `Rendered: ${component} →`, link: inject, time: buildTime(renderStart, renderEnd) })
72
77
  } catch (err) {
73
- console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed rendering:${pstyle.reset} ${pstyle.italic + pstyle.underline}${component}${pstyle.reset + pstyle.bell}`)
74
- console.log(err)
78
+ log({ tag: 'reactor', error: true, text: 'Failed rendering:', link: component })
79
+ console.error(err)
75
80
  } finally {
76
81
  if (fs.existsSync(tmpWrapper)) fs.unlinkSync(tmpWrapper)
77
82
  if (fs.existsSync(tmpBundle)) fs.unlinkSync(tmpBundle)
@@ -115,14 +120,14 @@ export const html = renderToString(React.createElement(Component));
115
120
  try {
116
121
  await build(opts)
117
122
  } catch (err) {
118
- console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling client:${pstyle.reset} ${pstyle.italic + pstyle.underline}${out}${pstyle.reset + pstyle.bell}`)
119
- console.log(err)
123
+ log({ tag: 'reactor', error: true, text: 'Failed compiling client:', link: out })
124
+ console.error(err)
120
125
  return
121
126
  }
122
127
  const esbuildEnd = performance.now()
123
128
 
124
- if (!options.justMinified) console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${out}${pstyle.reset} ${pstyle.greenBright}${fileSize(out)}${pstyle.reset} ${pstyle.green}(${buildTime(esbuildStart, esbuildEnd)})${pstyle.reset}`)
125
- if (options.sourcemap) console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${out}.map${pstyle.reset}`)
129
+ if (!options.justMinified) log({ tag: 'reactor', text: 'Compiled:', link: out, size: fileSize(out), time: buildTime(esbuildStart, esbuildEnd) })
130
+ if (options.sourcemap) log({ tag: 'reactor', text: 'Compiled:', link: `${out}.map` })
126
131
 
127
132
  if (options.minify) {
128
133
  const minPath = insertMinSuffix(out)
@@ -136,10 +141,10 @@ export const html = renderToString(React.createElement(Component));
136
141
 
137
142
  if (this.banner) minifyResult.code = this.banner + '\n' + minifyResult.code
138
143
  fs.writeFileSync(minPath, minifyResult.code)
139
- console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset} ${pstyle.greenBright}${fileSize(minPath)}${pstyle.reset} ${pstyle.green}(${buildTime(terserStart, terserEnd)})${pstyle.reset}`)
144
+ log({ tag: 'reactor', text: 'Compiled:', link: minPath, size: fileSize(minPath), time: buildTime(terserStart, terserEnd) })
140
145
  } catch (err) {
141
- console.log(`${pstyle.yellowBright + pstyle.bold}[ssg]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset + pstyle.bell}`)
142
- console.log(err)
146
+ log({ tag: 'reactor', error: true, text: 'Failed compiling:', link: minPath })
147
+ console.error(err)
143
148
  }
144
149
 
145
150
  if (options.justMinified) {
@@ -155,4 +160,10 @@ export const html = renderToString(React.createElement(Component));
155
160
  getRendered() {
156
161
  return this.rendered
157
162
  }
163
+
164
+ belongsToReactor(file) {
165
+ if (!this.config.reactor) return false
166
+ const entries = Array.isArray(this.config.reactor) ? this.config.reactor : [this.config.reactor]
167
+ return entries.some(e => e.component && pathContainsPathSegment(file, path.dirname(e.component)))
168
+ }
158
169
  }
package/lib/scripts.js CHANGED
@@ -11,19 +11,18 @@ import {
11
11
  fileSize
12
12
  } from './utils/helpers.js'
13
13
  import fs from 'node:fs'
14
- import PrintStyle from './utils/print-style.js'
15
-
16
- const pstyle = new PrintStyle()
14
+ import log from './utils/log.js'
17
15
 
18
16
  export default class Scripts {
19
17
  constructor(config) {
20
18
  this.config = config
21
- this.banner = fillBannerTemplate(config.banner, config.pkg)
19
+ this.banner = config.banner ? fillBannerTemplate(config.banner, config.pkg) : null
22
20
  }
23
21
 
24
22
  async compile() {
25
23
  if (!this.config.scripts) return
26
24
  this.config.scripts = Array.isArray(this.config.scripts) ? this.config.scripts : [this.config.scripts]
25
+
27
26
  for (const scriptEntry of this.config.scripts) {
28
27
  if (scriptEntry.in && scriptEntry.out && pathExists(scriptEntry.in)) {
29
28
  mkPath(scriptEntry.out)
@@ -32,7 +31,7 @@ export default class Scripts {
32
31
  }
33
32
  }
34
33
 
35
- async compileEntry(infilePath, outfilePath, options = {}) {
34
+ async compileEntry(infilePath, outfilePath, options = {}, tag = 'script') {
36
35
  if (!Array.isArray(infilePath)) infilePath = [infilePath]
37
36
 
38
37
  const opts = {
@@ -57,7 +56,7 @@ export default class Scripts {
57
56
  opts.outdir = outfilePath
58
57
  } else {
59
58
  if (infilePath.length > 1) {
60
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} Cannot output multiple ${pstyle.bold + pstyle.underline}script${pstyle.reset} files to a single file. Please specify an output directory path instead.`)
59
+ log({ tag: 'error', text: 'Cannot output multiple script files to a single file. Please specify an output directory path instead.' })
61
60
  process.exit(1)
62
61
  }
63
62
  opts.outfile = outfilePath
@@ -78,8 +77,8 @@ export default class Scripts {
78
77
  try {
79
78
  await build(opts)
80
79
  } catch (err) {
81
- console.log(`${pstyle.yellowBright + pstyle.bold}[script]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${outfilePath}${pstyle.reset + pstyle.bell}`)
82
- console.log(err)
80
+ log({ tag, error: true, text: 'Failed compiling:', link: outfilePath })
81
+ console.error(err)
83
82
  return
84
83
  }
85
84
  const esbuildEnd = performance.now()
@@ -88,8 +87,8 @@ export default class Scripts {
88
87
  const newOutFilePath = buildScriptOutputFilePath(entry, outfilePath)
89
88
  const minPath = insertMinSuffix(newOutFilePath)
90
89
 
91
- if (!options.justMinified) console.log(`${pstyle.yellowBright + pstyle.bold}[script]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${newOutFilePath}${pstyle.reset} ${pstyle.greenBright}${fileSize(newOutFilePath)}${pstyle.reset} ${pstyle.green}(${buildTime(esbuildStart, esbuildEnd)})${pstyle.reset}`)
92
- if (options.sourcemap) console.log(`${pstyle.yellowBright + pstyle.bold}[script]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${newOutFilePath}.map${pstyle.reset}`)
90
+ if (!options.justMinified) log({ tag, text: 'Compiled:', link: newOutFilePath, size: fileSize(newOutFilePath), time: buildTime(esbuildStart, esbuildEnd) })
91
+ if (options.sourcemap) log({ tag, text: 'Compiled:', link: `${newOutFilePath}.map` })
93
92
 
94
93
  if (options.minify) {
95
94
  try {
@@ -102,10 +101,10 @@ export default class Scripts {
102
101
 
103
102
  if (this.banner) minifyResult.code = this.banner + '\n' + minifyResult.code
104
103
  fs.writeFileSync(minPath, minifyResult.code)
105
- console.log(`${pstyle.yellowBright + pstyle.bold}[script]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset} ${pstyle.greenBright}${fileSize(minPath)}${pstyle.reset} ${pstyle.green}(${buildTime(terserStart, terserEnd)})${pstyle.reset}`)
104
+ log({ tag, text: 'Compiled:', link: minPath, size: fileSize(minPath), time: buildTime(terserStart, terserEnd) })
106
105
  } catch (err) {
107
- console.log(`${pstyle.yellowBright + pstyle.bold}[script]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset + pstyle.bell}`)
108
- console.log(err)
106
+ log({ tag, error: true, text: 'Failed compiling:', link: minPath })
107
+ console.error(err)
109
108
  }
110
109
 
111
110
  if (options.justMinified) {
package/lib/styles.js CHANGED
@@ -11,10 +11,9 @@ import {
11
11
  } from './utils/helpers.js'
12
12
  import path from 'node:path'
13
13
  import * as sass from 'sass'
14
- import PrintStyle from './utils/print-style.js'
15
- import sassPathResolver from 'sass-path-resolver'
16
-
17
- const pstyle = new PrintStyle()
14
+ import log from './utils/log.js'
15
+ import { sassPathResolver } from 'sass-path-resolver'
16
+ import { sassTokenImporter } from 'sass-token-importer'
18
17
 
19
18
  export default class Styles {
20
19
  constructor(config) {
@@ -36,10 +35,19 @@ export default class Styles {
36
35
  async compileEntry(infilePath, outfilePath, options = {}) {
37
36
  const includePaths = this.config.includePaths || []
38
37
 
38
+ const importers = [sassPathResolver(includePaths)]
39
+
40
+ if (options.tokenPaths) {
41
+ const tokenOpts = {}
42
+ if (options.tokenOutput) tokenOpts.output = options.tokenOutput
43
+ if (options.resolveAliases !== undefined) tokenOpts.resolveAliases = options.resolveAliases
44
+ importers.push(sassTokenImporter(options.tokenPaths, tokenOpts))
45
+ }
46
+
39
47
  const opts = {
40
48
  sourceMap: false,
41
49
  sourceMapIncludeSources: false,
42
- importers: [sassPathResolver(includePaths)]
50
+ importers
43
51
  }
44
52
 
45
53
  if (options.sourcemap) {
@@ -54,8 +62,8 @@ export default class Styles {
54
62
  try {
55
63
  compiledSass = sass.compile(infilePath, opts)
56
64
  } catch (err) {
57
- console.log(`${pstyle.magentaBright + pstyle.bold}[style]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${outfilePath}${pstyle.reset + pstyle.bell}`)
58
- console.log(err)
65
+ log({ tag: 'style', error: true, text: 'Failed compiling:', link: outfilePath })
66
+ console.error(err)
59
67
  return
60
68
  }
61
69
 
@@ -63,12 +71,12 @@ export default class Styles {
63
71
  if (this.banner) compiledSass.css = this.banner + '\n' + compiledSass.css
64
72
  fs.writeFileSync(outfilePath, compiledSass.css + mapsrc)
65
73
  const stylesEnd = performance.now()
66
- if (!options.justMinified) console.log(`${pstyle.magentaBright + pstyle.bold}[style]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${outfilePath}${pstyle.reset} ${pstyle.greenBright}${fileSize(outfilePath)}${pstyle.reset} ${pstyle.green}(${buildTime(stylesStart, stylesEnd)})${pstyle.reset}`)
74
+ if (!options.justMinified) log({ tag: 'style', text: 'Compiled:', link: outfilePath, size: fileSize(outfilePath), time: buildTime(stylesStart, stylesEnd) })
67
75
 
68
76
  if (compiledSass.sourceMap) {
69
77
  if (this.banner) compiledSass.sourceMap.mappings = ';' + compiledSass.sourceMap.mappings
70
78
  fs.writeFileSync(`${outfilePath}.map`, JSON.stringify(compiledSass.sourceMap))
71
- console.log(`${pstyle.magentaBright + pstyle.bold}[style]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${outfilePath}.map${pstyle.reset}`)
79
+ log({ tag: 'style', text: 'Compiled:', link: `${outfilePath}.map` })
72
80
  }
73
81
 
74
82
  const minPath = insertMinSuffix(outfilePath)
@@ -84,10 +92,10 @@ export default class Styles {
84
92
  if (this.banner) minified.code = this.banner + '\n' + minified.code
85
93
  fs.writeFileSync(minPath, minified.code)
86
94
  const stylesMinEnd = performance.now()
87
- console.log(`${pstyle.magentaBright + pstyle.bold}[style]${pstyle.reset} ${pstyle.dim}Compiled:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset} ${pstyle.greenBright}${fileSize(minPath)}${pstyle.reset} ${pstyle.green}(${buildTime(stylesMinStart, stylesMinEnd)})${pstyle.reset}`)
95
+ log({ tag: 'style', text: 'Compiled:', link: minPath, size: fileSize(minPath), time: buildTime(stylesMinStart, stylesMinEnd) })
88
96
  } catch (err) {
89
- console.log(`${pstyle.magentaBright + pstyle.bold}[style]${pstyle.reset} ${pstyle.redBright}[error]${pstyle.reset} ${pstyle.dim}Failed compiling:${pstyle.reset} ${pstyle.italic + pstyle.underline}${minPath}${pstyle.reset + pstyle.bell}`)
90
- console.log(err)
97
+ log({ tag: 'style', error: true, text: 'Failed compiling:', link: minPath })
98
+ console.error(err)
91
99
  }
92
100
 
93
101
  if (options.justMinified) {
@@ -4,8 +4,6 @@ import path from 'node:path'
4
4
  import yaml from 'yaml'
5
5
  import { convertGlobToRegex } from 'book-of-spells'
6
6
 
7
- const frontMatterCache = new Map()
8
-
9
7
  export function pathExists() {
10
8
  return fs.existsSync(path.join(...arguments))
11
9
  }
@@ -96,60 +94,6 @@ export function readYamlFile(filePath) {
96
94
  }
97
95
  }
98
96
 
99
- export function parseFrontMatter(filePath) {
100
- let stat
101
- try {
102
- stat = fs.statSync(filePath)
103
- } catch (e) {
104
- throw new Error(`Error stating file at ${filePath}: ${e.message}`)
105
- }
106
-
107
- const cached = frontMatterCache.get(filePath)
108
- if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
109
- return { frontMatter: { ...cached.value.frontMatter }, content: cached.value.content }
110
- }
111
-
112
- let content = ''
113
- try {
114
- content = fs.readFileSync(filePath, 'utf8')
115
- } catch (e) {
116
- throw new Error(`Error reading file at ${filePath}: ${e.message}`)
117
- }
118
-
119
- if (!content) {
120
- throw new Error(`File at ${filePath} is empty`)
121
- }
122
-
123
- const frontMatterRegex = /^\s*---\s*[\r\n]+([\s\S]*?)\s*---\s*[\r\n]+/
124
- const match = content.match(frontMatterRegex)
125
-
126
- if (!match) {
127
- const value = { frontMatter: {}, content }
128
- frontMatterCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, value })
129
- return { frontMatter: {}, content }
130
- }
131
-
132
- let frontMatter = {}
133
- try {
134
- frontMatter = yaml.parse(match[1])
135
- } catch (e) {
136
- throw new Error(`Error parsing front matter in file at ${filePath}: ${e.message}`)
137
- }
138
-
139
- const contentWithoutFrontMatter = content.slice(match[0].length)
140
- const value = { frontMatter, content: contentWithoutFrontMatter }
141
- frontMatterCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, value })
142
- return { frontMatter: { ...frontMatter }, content: contentWithoutFrontMatter }
143
- }
144
-
145
- export function clearFrontMatterCache(filePath) {
146
- if (!filePath) {
147
- frontMatterCache.clear()
148
- return
149
- }
150
- frontMatterCache.delete(filePath)
151
- }
152
-
153
97
  export function readDataFile(filePath) {
154
98
  if (/(\.json)$/i.test(filePath)) return readJsonFile(filePath)
155
99
  if (/(\.ya?ml)$/i.test(filePath)) return readYamlFile(filePath)
@@ -0,0 +1,64 @@
1
+ import PrintStyle from 'printstyle'
2
+
3
+ const pstyle = new PrintStyle()
4
+
5
+ const TAG_COLORS = {
6
+ script: 'yellowBright',
7
+ reactor: 'yellowBright',
8
+ style: 'magentaBright',
9
+ markup: 'cyanBright',
10
+ copy: 'green',
11
+ info: 'blue',
12
+ error: 'redBright'
13
+ }
14
+
15
+ const DEFAULT_COLOR = 'white'
16
+
17
+ /**
18
+ * @param {Object} options
19
+ * @param {string} options.tag - Tag label (e.g. 'script', 'style', 'markup', 'reactor', 'copy', 'info')
20
+ * @param {boolean} [options.error] - Whether this is an error message
21
+ * @param {string} options.text - Main message text
22
+ * @param {string} [options.link] - File path or URL to display underlined
23
+ * @param {string} [options.size] - File size string
24
+ * @param {string} [options.time] - Build time string
25
+ */
26
+ export default function log({ tag, error, text, link, size, time }) {
27
+ if (tag === 'error') error = true
28
+ const color = TAG_COLORS[tag] || DEFAULT_COLOR
29
+ let msg = pstyle.paint(`{${color}.bold|[${tag}]}`)
30
+
31
+ if (error) {
32
+ msg += pstyle.paint(' {redBright.bold|[error]}')
33
+ }
34
+
35
+ msg += pstyle.paint(` {dim|${text}}`)
36
+
37
+ if (link) {
38
+ msg += pstyle.paint(` {italic.underline|${link}}`)
39
+ }
40
+
41
+ if (size) {
42
+ msg += pstyle.paint(` {greenBright|${size}}`)
43
+ }
44
+
45
+ if (time) {
46
+ msg += pstyle.paint(` {green|(${time})}`)
47
+ }
48
+
49
+ if (error) {
50
+ msg += pstyle.bell
51
+ }
52
+
53
+ if (error) {
54
+ console.error(msg)
55
+ } else {
56
+ console.log(msg)
57
+ }
58
+ }
59
+
60
+ export const bell = pstyle.bell
61
+
62
+ export function styledLog(template) {
63
+ console.log(pstyle.paint(template))
64
+ }
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "poops",
3
3
  "description": "Straightforward, no-bullshit bundler for the web.",
4
- "version": "1.1.0",
4
+ "version": "1.2.0",
5
5
  "license": "MIT",
6
6
  "type": "module",
7
7
  "main": "poops.js",
@@ -26,40 +26,58 @@
26
26
  "javascript",
27
27
  "sass",
28
28
  "nunjucks",
29
+ "liquid",
29
30
  "esbuild",
30
31
  "dart-sass",
32
+ "tailwindcss",
31
33
  "toolchain-script",
32
34
  "static-site-generator"
33
35
  ],
36
+ "peerDependencies": {
37
+ "postcss": "^8.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "postcss": {
41
+ "optional": true
42
+ }
43
+ },
34
44
  "scripts": {
35
45
  "build": "node ./poops.js -b",
36
46
  "lint": "eslint ./poops.js",
37
47
  "test": "NODE_OPTIONS='--experimental-vm-modules' jest"
38
48
  },
39
49
  "dependencies": {
50
+ "argoyle": "^1.0.0",
40
51
  "book-of-spells": "^1.0.32",
41
52
  "chokidar": "^3.5.3",
42
53
  "connect": "^3.7.0",
54
+ "dayjs": "^1.11.19",
43
55
  "esbuild": "^0.25.0",
44
56
  "glob": "^13.0.6",
57
+ "highlight.js": "^11.11.1",
58
+ "liquidjs": "^10.24.0",
45
59
  "livereload": "^0.9.3",
46
60
  "marked": "^9.0.3",
47
- "moment": "^2.29.4",
48
61
  "nunjucks": "^3.2.4",
49
62
  "portscanner": "^2.2.0",
63
+ "printstyle": "^1.0.0",
50
64
  "sass": "^1.63.4",
51
65
  "sass-path-resolver": "^1.0.2",
66
+ "sass-token-importer": "^1.0.0",
52
67
  "serve-static": "^1.15.0",
53
68
  "yaml": "^2.3.1"
54
69
  },
55
70
  "devDependencies": {
56
71
  "@jest/globals": "^30.2.0",
72
+ "@tailwindcss/postcss": "^4.2.1",
57
73
  "eslint": "^9.39.3",
58
74
  "jest": "^30.2.0",
59
75
  "neostandard": "^0.12.2",
76
+ "postcss": "^8.5.8",
60
77
  "react": "^19.2.4",
61
78
  "react-dom": "^19.2.4",
62
- "sulphuris": "^2.0.0"
79
+ "sulphuris": "^2.0.0",
80
+ "tailwindcss": "^4.2.1"
63
81
  },
64
82
  "overrides": {
65
83
  "minimatch": ">=10.2.1"