poops 1.0.20 → 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
@@ -1,193 +1,168 @@
1
1
  #!/usr/bin/env node
2
2
 
3
- const chokidar = require('chokidar')
4
- const connect = require('connect')
5
- const Copy = require('./lib/copy.js')
6
- const helpers = require('./lib/utils/helpers.js')
7
- const http = require('node:http')
8
- const livereload = require('livereload')
9
- const Markups = require('./lib/markups.js')
10
- const path = require('node:path')
11
- const serveStatic = require('serve-static')
12
- const Scripts = require('./lib/scripts.js')
13
- const PrintStyle = require('./lib/utils/print-style.js')
14
- const Styles = require('./lib/styles.js')
15
- const portscanner = require('portscanner')
16
-
17
- const { pathExists, doesFileBelongToPath } = helpers
3
+ import chokidar from 'chokidar'
4
+ import connect from 'connect'
5
+ import Copy from './lib/copy.js'
6
+ import { pathExists, doesFileBelongToPath } from './lib/utils/helpers.js'
7
+ import http from 'node:http'
8
+ import os from 'node:os'
9
+ import fs from 'node:fs'
10
+ import livereload from 'livereload'
11
+ import Markups from './lib/markups.js'
12
+ import path from 'node:path'
13
+ import serveStatic from 'serve-static'
14
+ import Reactor from './lib/reactor.js'
15
+ import Scripts from './lib/scripts.js'
16
+ import log, { styledLog } from './lib/utils/log.js'
17
+ import Styles from './lib/styles.js'
18
+ import PostCSS from './lib/postcss.js'
19
+ import Argoyle from 'argoyle'
20
+ import portscanner from 'portscanner'
18
21
 
19
22
  const cwd = process.cwd() // Current Working Directory
20
- const pkg = require('./package.json')
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
- }
23
+ const pkg = JSON.parse(fs.readFileSync(new URL('./package.json', import.meta.url), 'utf-8'))
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
 
92
- // Main function 💩
93
- async function poops() {
94
- let lport
95
- if (config.livereload) {
96
- lport = overrideLivereloadPort || config.livereload.port || 35729
97
- if (!overrideLivereloadPort) lport = await getAvailablePort(lport, lport + 10)
98
- config.livereload_port = lport
48
+ async function resolveLiveReloadPort(config) {
49
+ if (!config.livereload) return null
50
+ let liveReloadPort = overrideLivereloadPort || config.livereload.port || 35729
51
+ if (!overrideLivereloadPort) liveReloadPort = await getAvailablePort(liveReloadPort, liveReloadPort + 10)
52
+ config.livereload_port = liveReloadPort
53
+ }
54
+
55
+ function setupLiveReloadServer(config) {
56
+ if (!config.livereload) return null
57
+ const liveReloadExcludes = ['.git', '.svn', '.hg']
58
+
59
+ if (config.watch) {
60
+ liveReloadExcludes.push(...config.watch)
99
61
  }
100
62
 
101
- const styles = new Styles(config)
102
- const scripts = new Scripts(config)
103
- const markups = new Markups(config)
104
- const copy = new Copy(config)
63
+ if (config.includePaths) {
64
+ liveReloadExcludes.push(...config.includePaths)
65
+ }
105
66
 
106
- if (build || (!config.watch && !config.livereload && !config.serve)) {
107
- await styles.compile()
108
- await scripts.compile()
109
- await markups.compile()
110
- await copy.execute()
111
- process.exit(0)
67
+ if (config.livereload.exclude) {
68
+ liveReloadExcludes.push(...config.livereload.exclude)
112
69
  }
113
70
 
114
- if (config.livereload) {
115
- const lrExcludes = ['.git', '.svn', '.hg']
71
+ const liveReloadServer = livereload.createServer({
72
+ exclusions: [...new Set(liveReloadExcludes)],
73
+ port: config.livereload_port
74
+ })
75
+ styledLog(`🔃 {dim}LiveReload :{/} ${liveReloadServer.config.port}`)
76
+ console.log()
77
+ liveReloadServer.watch(cwd)
78
+ }
116
79
 
117
- if (config.watch) {
118
- lrExcludes.push(...config.watch)
80
+ function setupWatchers(config, modules) {
81
+ if (!config.watch) return
82
+
83
+ // TODO: think about watching the updates of the config file itself, we can reload the config and recompile everything.
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.
85
+ chokidar.watch(config.watch, { ignoreInitial: true }).on('change', (file) => {
86
+ if (/(\.m?jsx?|\.tsx?)$/i.test(file)) {
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))
96
+ }
119
97
  }
120
-
121
- if (config.includePaths) {
122
- lrExcludes.push(...config.includePaths)
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))
123
103
  }
124
104
 
125
- if (config.livereload.exclude) {
126
- lrExcludes.push(...config.livereload.exclude)
105
+ // TODO: We can actually reload the page only if the data file from data has changed.
106
+ if (/(\.json|\.ya?ml)$/i.test(file)) {
107
+ modules.markups.reloadDataFiles().then(() => modules.markups.compile()).catch(err => console.error(err))
127
108
  }
128
109
 
129
- const lrserver = livereload.createServer({
130
- exclusions: [...new Set(lrExcludes)],
131
- port: lport
132
- })
133
- console.log(`${pstyle.blue + pstyle.bold}[info]${pstyle.reset} 🔃${pstyle.dim} LiveReload server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${lrserver.config.port}${pstyle.reset}\n`)
134
- lrserver.watch(cwd)
135
- }
110
+ doesFileBelongToPath(file, config.copy) && modules.copy.execute().catch(err => console.error(err))
111
+ }).on('unlink', (file) => {
112
+ if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.liquid|\.md)$/i.test(file)) {
113
+ modules.markups.compile().catch(err => console.error(err))
114
+ }
115
+ modules.copy.unlink(file, doesFileBelongToPath(file, 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))
119
+ }).on('add', (file) => {
120
+ if (/(\.json|\.ya?ml)$/i.test(file)) {
121
+ modules.markups.reloadDataFiles().then(() => modules.markups.compile()).catch(err => console.error(err))
122
+ }
123
+ doesFileBelongToPath(file, config.copy) && modules.copy.execute().catch(err => console.error(err))
124
+ })
125
+ }
136
126
 
137
- await styles.compile()
138
- await scripts.compile()
139
- await markups.compile()
140
- await copy.execute()
127
+ // Main function 💩
128
+ async function poops() {
129
+ const styles = new Styles(config)
130
+ const postcss = new PostCSS(config)
131
+ const reactor = new Reactor(config)
132
+ const scripts = new Scripts(config)
133
+ const markups = new Markups(config)
134
+ const copy = new Copy(config)
141
135
 
142
- if (config.watch) {
143
- // TODO: think about watching the updates of the config file itself, we can reload the config and recompile everything.
144
- // 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.
145
- chokidar.watch(config.watch, { ignoreInitial: true }).on('change', (file) => {
146
- if (/(\.m?js|\.ts)$/i.test(file)) scripts.compile()
147
- if (/(\.sass|\.scss|\.css)$/i.test(file)) styles.compile()
148
- if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.md)$/i.test(file)) markups.compile()
149
-
150
- // TODO: We can actually reload the page only if the data file from data has changed.
151
- if (/(\.json|\.ya?ml)$/i.test(file)) {
152
- markups.reloadDataFiles().then(() => {
153
- markups.compile()
154
- })
155
- }
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) }
156
143
 
157
- doesFileBelongToPath(file, config.copy) && copy.execute()
158
- }).on('unlink', (file) => {
159
- if (/(\.html|\.xml|\.rss|\.atom|\.njk|\.md)$/i.test(file)) markups.compile()
160
- copy.unlink(file, doesFileBelongToPath(file, config.copy))
161
- }).on('unlinkDir', (path) => {
162
- doesFileBelongToPath(path, config.markup) && markups.compile()
163
- copy.unlink(path, doesFileBelongToPath(path, config.copy))
164
- }).on('add', (file) => {
165
- if (/(\.json|\.ya?ml)$/i.test(file)) {
166
- markups.reloadDataFiles().then(() => {
167
- markups.compile()
168
- })
169
- }
170
- doesFileBelongToPath(file, config.copy) && copy.execute()
171
- })
144
+ if (build || (!config.watch && !config.livereload && !config.serve)) {
145
+ process.exit(0)
172
146
  }
147
+
148
+ setupWatchers(config, { styles, postcss, reactor, scripts, markups, copy })
173
149
  }
174
150
 
175
151
  // CLI Header
176
152
  const title = `💩 Poops — v${pkg.version}`
177
- console.log(`\n${pstyle.color('#8b4513')}${title}
178
- ${title.replace(/./g, '-')}${pstyle.reset + pstyle.bell}\n`)
153
+ styledLog(`\n{#8b4513}${title}\n${title.replace(/./g, '-')}{/}{bell}\n`)
179
154
 
180
155
  // Check if poops.json exists
181
156
  if (!pathExists(configPath)) {
182
- console.log(`${pstyle.redBright + pstyle.bold}[error]${pstyle.reset} \`${pstyle.underline}${defaultConfigPath}${pstyle.reset}\` or \`${pstyle.underline}💩.json${pstyle.reset}\` not found.
183
- ${pstyle.dim}Configuration file \`${defaultConfigPath}\` or \`💩.json\` not found in your working directory: ${pstyle.underline}${cwd}${pstyle.reset}\n
184
- ${pstyle.dim}Please specify another file path or create a \`poops.json\` or \`💩.json\` file in your working directory and try again.\n
185
- ${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`)
186
161
  process.exit(1)
187
162
  }
188
163
 
189
164
  // Load poops.json
190
- const config = require(configPath)
165
+ const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
191
166
 
192
167
  if (config.watch) {
193
168
  config.watch = Array.isArray(config.watch) ? config.watch : [config.watch]
@@ -199,6 +174,11 @@ if (config.includePaths) {
199
174
  config.includePaths = ['node_modules']
200
175
  }
201
176
 
177
+ // Backwards compatibility: support "ssg" as alias for "reactor"
178
+ if (!config.reactor && config.ssg) {
179
+ config.reactor = config.ssg
180
+ }
181
+
202
182
  async function getAvailablePort(port, max) {
203
183
  while (port < max) {
204
184
  const status = await portscanner.checkPortStatus(port, 'localhost')
@@ -211,7 +191,19 @@ async function getAvailablePort(port, max) {
211
191
  return port
212
192
  }
213
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
+
214
204
  async function startServer() {
205
+ await resolveLiveReloadPort(config)
206
+ await poops() // Initial compilation before starting the server
215
207
  const app = connect()
216
208
 
217
209
  if (config.serve.base && pathExists(cwd, config.serve.base)) {
@@ -223,9 +215,12 @@ async function startServer() {
223
215
  let port = overridePort || config.serve.port || 4040
224
216
  if (!overridePort) port = await getAvailablePort(port, port + 10)
225
217
 
226
- http.createServer(app).listen(parseInt(port), () => {
227
- console.log(`${pstyle.blue + pstyle.bold}[info]${pstyle.reset} 🌍${pstyle.dim} Local server:${pstyle.reset} ${pstyle.italic + pstyle.underline}http://localhost:${port}${pstyle.reset}`)
228
- poops()
218
+ // eslint-disable-next-line @stylistic/space-before-function-paren
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}}`)
223
+ setupLiveReloadServer(config)
229
224
  })
230
225
  }
231
226
 
@@ -1,72 +0,0 @@
1
- module.exports = 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
- }