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/poops.js CHANGED
@@ -5,87 +5,43 @@ import connect from 'connect'
5
5
  import Copy from './lib/copy.js'
6
6
  import { pathExists, doesFileBelongToPath } from './lib/utils/helpers.js'
7
7
  import http from 'node:http'
8
+ import os from 'node:os'
8
9
  import fs from 'node:fs'
9
10
  import livereload from 'livereload'
10
11
  import Markups from './lib/markups.js'
11
12
  import path from 'node:path'
12
13
  import serveStatic from 'serve-static'
14
+ import Reactor from './lib/reactor.js'
13
15
  import Scripts from './lib/scripts.js'
14
- import SSG from './lib/ssg.js'
15
- import PrintStyle from './lib/utils/print-style.js'
16
+ import log, { styledLog } from './lib/utils/log.js'
16
17
  import Styles from './lib/styles.js'
18
+ import PostCSS from './lib/postcss.js'
19
+ import Argoyle from 'argoyle'
17
20
  import portscanner from 'portscanner'
18
21
 
19
22
  const cwd = process.cwd() // Current Working Directory
20
23
  const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
21
- const args = process.argv.slice(2)
22
- const pstyle = new PrintStyle()
23
-
24
- let build = false
25
- let defaultConfigPath = 'poops.json'
26
- let overridePort = null
27
- let overrideLivereloadPort = null
28
-
29
- for (let i = 0; i < args.length; i++) {
30
- const arg = args[i]
31
- switch (arg) {
32
- case '-b':
33
- case '--build':
34
- build = true
35
- break
36
- case '-c':
37
- case '--config':
38
- if (args.length === i + 1 || args[i + 1].startsWith('-')) {
39
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} Missing config file path`)
40
- process.exit(1)
41
- }
42
- defaultConfigPath = args[i + 1]
43
- i++
44
- break
45
- case '-p':
46
- case '--port':
47
- if (args.length === i + 1 || args[i + 1].startsWith('-') || isNaN(args[i + 1])) {
48
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} Missing port number`)
49
- process.exit(1)
50
- }
51
- overridePort = args[i + 1]
52
- i++
53
- break
54
- case '-l':
55
- case '--livereload':
56
- if (args.length === i + 1 || args[i + 1].startsWith('-') || isNaN(args[i + 1])) {
57
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} Missing livereload port number`)
58
- process.exit(1)
59
- }
60
- overrideLivereloadPort = args[i + 1]
61
- i++
62
- break
63
- case '-v':
64
- case '--version':
65
- console.log(pkg.version)
66
- process.exit(0)
67
- break
68
- case '-h':
69
- case '--help':
70
- console.log(`Usage: ${pkg.name} [config-file] [options]
71
- -b, --build\t\tBuild the project and exit
72
- -c, --config\t\tSpecify the config file
73
- -h, --help\t\tShow this help message
74
- -l, --livereload\t\tSpecify the port to use for the livereload server, overrides the config file
75
- -p, --port\t\tSpecify the port to use for the server, overrides the config file
76
- -v, --version\t\tShow version number`)
77
- process.exit(0)
78
- break
79
- default:
80
- if (arg.startsWith('-')) {
81
- console.log(`Unknown option: ${arg}`)
82
- process.exit(1)
83
- } else {
84
- defaultConfigPath = arg
85
- }
86
- }
24
+
25
+ const cli = new Argoyle(pkg.version)
26
+ .line(`Usage: ${pkg.name} [config-file] [options]\n`)
27
+ .option('build', { short: 'b', description: 'Build the project and exit' })
28
+ .option('config', { short: 'c', value: '<path>', description: 'Specify the config file' })
29
+ .option('port', { short: 'p', value: '<number>', description: 'Specify the port for the server, overrides the config file' })
30
+ .option('livereload-port', { short: 'l', value: '<number>', description: 'Specify the port for the livereload server, overrides the config file' })
31
+
32
+ let flags, positionals
33
+ try {
34
+ ({ flags, positionals } = cli.parse())
35
+ } catch (err) {
36
+ log({ tag: 'error', text: err.message })
37
+ process.exit(1)
87
38
  }
88
39
 
40
+ const build = flags.build
41
+ const defaultConfigPath = flags.config || positionals[0] || 'poops.json'
42
+ const overridePort = flags.port
43
+ const overrideLivereloadPort = flags['livereload-port']
44
+
89
45
  let configPath = path.join(cwd, defaultConfigPath)
90
46
  if (!pathExists(configPath)) configPath = path.join(cwd, '💩.json') // TODO: Ok dude, I know it's late, but you can do better than this.
91
47
 
@@ -116,7 +72,8 @@ function setupLiveReloadServer(config) {
116
72
  exclusions: [...new Set(liveReloadExcludes)],
117
73
  port: config.livereload_port
118
74
  })
119
- console.log(`${pstyle.blue + pstyle.bold}[info]${pstyle.reset} 🔃${pstyle.dim} LiveReload server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${liveReloadServer.config.port}${pstyle.reset}\n`)
75
+ styledLog(`🔃 {dim}LiveReload :{/} ${liveReloadServer.config.port}`)
76
+ console.log()
120
77
  liveReloadServer.watch(cwd)
121
78
  }
122
79
 
@@ -127,74 +84,80 @@ function setupWatchers(config, modules) {
127
84
  // TODO: ability to automatically create a watch list of directories if watch is set to true. The list will be generated from the `in` property of each task.
128
85
  chokidar.watch(config.watch, { ignoreInitial: true }).on('change', (file) => {
129
86
  if (/(\.m?jsx?|\.tsx?)$/i.test(file)) {
130
- modules.scripts.compile()
131
- if (config.ssg) {
132
- modules.ssg.compile().then(() => {
133
- config.ssgData = modules.ssg.getRendered()
134
- modules.markups.compile()
135
- })
87
+ modules.scripts.compile().catch(err => console.error(err))
88
+
89
+ if (modules.reactor.belongsToReactor(file)) {
90
+ modules.reactor.compile().then(() => {
91
+ if (modules.reactor.renderedChanged) {
92
+ config.reactorData = modules.reactor.getRendered()
93
+ modules.markups.compile().then(() => modules.postcss.compile()).catch(err => console.error(err))
94
+ }
95
+ }).catch(err => console.error(err))
136
96
  }
137
97
  }
138
- if (/(\.sass|\.scss|\.css)$/i.test(file)) modules.styles.compile()
139
- if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.md)$/i.test(file)) modules.markups.compile()
98
+ if (/(\.sass|\.scss|\.css)$/i.test(file)) {
99
+ modules.styles.compile().then(() => modules.postcss.compile()).catch(err => console.error(err))
100
+ }
101
+ if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.liquid|\.md)$/i.test(file)) {
102
+ modules.markups.compile().then(() => modules.postcss.compile()).catch(err => console.error(err))
103
+ }
140
104
 
141
105
  // TODO: We can actually reload the page only if the data file from data has changed.
142
106
  if (/(\.json|\.ya?ml)$/i.test(file)) {
143
- modules.markups.reloadDataFiles().then(() => {
144
- modules.markups.compile()
145
- })
107
+ modules.markups.reloadDataFiles().then(() => modules.markups.compile()).catch(err => console.error(err))
146
108
  }
147
109
 
148
- doesFileBelongToPath(file, config.copy) && modules.copy.execute()
110
+ doesFileBelongToPath(file, config.copy) && modules.copy.execute().catch(err => console.error(err))
149
111
  }).on('unlink', (file) => {
150
- if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.md)$/i.test(file)) modules.markups.compile()
112
+ if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.liquid|\.md)$/i.test(file)) {
113
+ modules.markups.compile().catch(err => console.error(err))
114
+ }
151
115
  modules.copy.unlink(file, doesFileBelongToPath(file, config.copy))
152
- }).on('unlinkDir', (path) => {
153
- doesFileBelongToPath(path, config.markup) && modules.markups.compile()
154
- modules.copy.unlink(path, doesFileBelongToPath(path, config.copy))
116
+ }).on('unlinkDir', (dirPath) => {
117
+ doesFileBelongToPath(dirPath, config.markup) && modules.markups.compile().catch(err => console.error(err))
118
+ modules.copy.unlink(dirPath, doesFileBelongToPath(dirPath, config.copy))
155
119
  }).on('add', (file) => {
156
120
  if (/(\.json|\.ya?ml)$/i.test(file)) {
157
- modules.markups.reloadDataFiles().then(() => {
158
- modules.markups.compile()
159
- })
121
+ modules.markups.reloadDataFiles().then(() => modules.markups.compile()).catch(err => console.error(err))
160
122
  }
161
- doesFileBelongToPath(file, config.copy) && modules.copy.execute()
123
+ doesFileBelongToPath(file, config.copy) && modules.copy.execute().catch(err => console.error(err))
162
124
  })
163
125
  }
164
126
 
165
127
  // Main function 💩
166
128
  async function poops() {
167
129
  const styles = new Styles(config)
168
- const ssg = new SSG(config)
130
+ const postcss = new PostCSS(config)
131
+ const reactor = new Reactor(config)
169
132
  const scripts = new Scripts(config)
170
133
  const markups = new Markups(config)
171
134
  const copy = new Copy(config)
172
135
 
173
- try { await styles.compile() } catch (err) { console.log(err) }
174
- try { await ssg.compile() } catch (err) { console.log(err) }
175
- config.ssgData = ssg.getRendered()
176
- try { await scripts.compile() } catch (err) { console.log(err) }
177
- try { await markups.compile() } catch (err) { console.log(err) }
178
- try { await copy.execute() } catch (err) { console.log(err) }
136
+ try { await styles.compile() } catch (err) { console.error(err) }
137
+ try { await reactor.compile() } catch (err) { console.error(err) }
138
+ config.reactorData = reactor.getRendered()
139
+ try { await scripts.compile() } catch (err) { console.error(err) }
140
+ try { await markups.compile() } catch (err) { console.error(err) }
141
+ try { await postcss.compile() } catch (err) { console.error(err) }
142
+ try { await copy.execute() } catch (err) { console.error(err) }
179
143
 
180
144
  if (build || (!config.watch && !config.livereload && !config.serve)) {
181
145
  process.exit(0)
182
146
  }
183
147
 
184
- setupWatchers(config, { styles, ssg, scripts, markups, copy })
148
+ setupWatchers(config, { styles, postcss, reactor, scripts, markups, copy })
185
149
  }
186
150
 
187
151
  // CLI Header
188
152
  const title = `💩 Poops — v${pkg.version}`
189
- console.log(`\n${pstyle.color('#8b4513')}${title}
190
- ${title.replace(/./g, '-')}${pstyle.reset + pstyle.bell}\n`)
153
+ styledLog(`\n{#8b4513}${title}\n${title.replace(/./g, '-')}{/}{bell}\n`)
191
154
 
192
155
  // Check if poops.json exists
193
156
  if (!pathExists(configPath)) {
194
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} \`${pstyle.underline}${defaultConfigPath}${pstyle.reset}\` or \`${pstyle.underline}💩.json${pstyle.reset}\` not found.
195
- ${pstyle.dim}Configuration file \`${defaultConfigPath}\` or \`💩.json\` not found in your working directory: ${pstyle.underline}${cwd}${pstyle.reset}\n
196
- ${pstyle.dim}Please specify another file path or create a \`poops.json\` or \`💩.json\` file in your working directory and try again.\n
197
- ${pstyle.dim}For information on the structure of the configuration file, please visit: \n${pstyle.underline}https://stamat.github.io/poops${pstyle.reset}\n`)
157
+ styledLog(`{bold.redBright|[error]} \`{underline|${defaultConfigPath}}\` or \`{underline|💩.json}\` not found.
158
+ {dim}Configuration file \`${defaultConfigPath}\` or \`💩.json\` not found in your working directory: {underline}${cwd}{/}{dim}\n
159
+ {/}{dim}Please specify another file path or create a \`poops.json\` or \`💩.json\` file in your working directory and try again.\n
160
+ {/}{dim}For information on the structure of the configuration file, please visit: \n{underline}https://stamat.github.io/poops{/}\n`)
198
161
  process.exit(1)
199
162
  }
200
163
 
@@ -211,6 +174,11 @@ if (config.includePaths) {
211
174
  config.includePaths = ['node_modules']
212
175
  }
213
176
 
177
+ // Backwards compatibility: support "ssg" as alias for "reactor"
178
+ if (!config.reactor && config.ssg) {
179
+ config.reactor = config.ssg
180
+ }
181
+
214
182
  async function getAvailablePort(port, max) {
215
183
  while (port < max) {
216
184
  const status = await portscanner.checkPortStatus(port, 'localhost')
@@ -223,7 +191,19 @@ async function getAvailablePort(port, max) {
223
191
  return port
224
192
  }
225
193
 
194
+ function getLocalIP() {
195
+ const interfaces = os.networkInterfaces()
196
+ for (const iface of Object.values(interfaces)) {
197
+ for (const info of iface) {
198
+ if (info.family === 'IPv4' && !info.internal) return info.address
199
+ }
200
+ }
201
+ return 'localhost'
202
+ }
203
+
226
204
  async function startServer() {
205
+ await resolveLiveReloadPort(config)
206
+ await poops() // Initial compilation before starting the server
227
207
  const app = connect()
228
208
 
229
209
  if (config.serve.base && pathExists(cwd, config.serve.base)) {
@@ -236,10 +216,10 @@ async function startServer() {
236
216
  if (!overridePort) port = await getAvailablePort(port, port + 10)
237
217
 
238
218
  // eslint-disable-next-line @stylistic/space-before-function-paren
239
- http.createServer(app).listen(parseInt(port), async () => {
240
- await resolveLiveReloadPort(config)
241
- await poops() // Initial compilation before starting the server
242
- console.log(`\n${pstyle.blue + pstyle.bold}[info]${pstyle.reset} 🌍${pstyle.dim} Local server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${port}${pstyle.reset}`)
219
+ http.createServer(app).listen(parseInt(port), '0.0.0.0', async () => {
220
+ console.log()
221
+ styledLog(`🏠 {dim}Local server:{/} {underline|http://localhost:${port}}`)
222
+ styledLog(`🛜 {dim} Network :{/} {underline|http://${getLocalIP()}:${port}}`)
243
223
  setupLiveReloadServer(config)
244
224
  })
245
225
  }
@@ -1,72 +0,0 @@
1
- export default class PrintStyle {
2
- reset = '\x1b[0m'
3
- bold = '\x1b[1m'
4
- dim = '\x1b[2m'
5
- italic = '\x1b[3m'
6
- underline = '\x1b[4m'
7
- blink = '\x1b[5m'
8
- inverse = '\x1b[7m'
9
- hidden = '\x1b[8m'
10
- strikethrough = '\x1b[9m'
11
- black = '\x1b[30m'
12
- red = '\x1b[31m'
13
- redBright = '\x1b[91m'
14
- green = '\x1b[32m'
15
- greenBright = '\x1b[92m'
16
- yellow = '\x1b[33m'
17
- yellowBright = '\x1b[93m'
18
- blue = '\x1b[34m'
19
- blueBright = '\x1b[94m'
20
- magenta = '\x1b[35m'
21
- magentaBright = '\x1b[95m'
22
- cyan = '\x1b[36m'
23
- cyanBright = '\x1b[96m'
24
- white = '\x1b[37m'
25
- whiteBright = '\x1b[97m'
26
- gray = '\x1b[90m'
27
- bgBlack = '\x1b[40m'
28
- bgRed = '\x1b[41m'
29
- bgRedBright = '\x1b[101m'
30
- bgGreen = '\x1b[42m'
31
- bgGreenBright = '\x1b[102m'
32
- bgYellow = '\x1b[43m'
33
- bgYellowBright = '\x1b[103m'
34
- bgBlue = '\x1b[44m'
35
- bgBlueBright = '\x1b[104m'
36
- bgMagenta = '\x1b[45m'
37
- bgMagentaBright = '\x1b[105m'
38
- bgCyan = '\x1b[46m'
39
- bgCyanBright = '\x1b[106m'
40
- bgWhite = '\x1b[47m'
41
- bgWhiteBright = '\x1b[107m'
42
- bgGray = '\x1b[100m'
43
- bell = '\x07'
44
-
45
- hexToRgb(hex) {
46
- const sanitizedHex = hex.replace('#', '')
47
- const red = parseInt(sanitizedHex.substring(0, 2), 16)
48
- const green = parseInt(sanitizedHex.substring(2, 4), 16)
49
- const blue = parseInt(sanitizedHex.substring(4, 6), 16)
50
-
51
- return [red, green, blue]
52
- }
53
-
54
- terminalColorIndex(red, green, blue) {
55
- return 16 +
56
- Math.round(red / 255 * 5) * 36 +
57
- Math.round(green / 255 * 5) * 6 +
58
- Math.round(blue / 255 * 5)
59
- }
60
-
61
- color(hex) {
62
- const [red, green, blue] = this.hexToRgb(hex)
63
-
64
- return `\x1b[38;5;${this.terminalColorIndex(red, green, blue)}m`
65
- }
66
-
67
- background(hex) {
68
- const [red, green, blue] = this.hexToRgb(hex)
69
-
70
- return `\x1b[48;5;${this.terminalColorIndex(red, green, blue)}m`
71
- }
72
- }