poops 1.9.8 → 2.0.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
@@ -8,12 +8,11 @@ import http from 'node:http'
8
8
  import net from 'node:net'
9
9
  import os from 'node:os'
10
10
  import fs from 'node:fs'
11
- import livereload from 'livereload'
12
11
  import Markups from './lib/markups.js'
13
12
  import Images from './lib/images.js'
14
13
  import path from 'node:path'
15
14
  import Reactor from './lib/reactor.js'
16
- import { createStaticHandler } from './lib/server.js'
15
+ import { createStaticHandler, createReloadHub } from './lib/server.js'
17
16
  import Scripts from './lib/scripts.js'
18
17
  import log, { styledLog, hasLoggedErrors } from './lib/utils/log.js'
19
18
  import Styles from './lib/styles.js'
@@ -28,7 +27,6 @@ const cli = new Argoyle(pkg.version)
28
27
  .option('build', { short: 'b', description: 'Build the project and exit' })
29
28
  .option('config', { short: 'c', value: '<path>', description: 'Specify the config file' })
30
29
  .option('port', { short: 'p', value: '<number>', description: 'Specify the port for the server, overrides the config file' })
31
- .option('livereload-port', { short: 'l', value: '<number>', description: 'Specify the port for the livereload server, overrides the config file' })
32
30
  .option('base-url', { short: 'u', value: '<path>', description: 'Set the base URL prefix for markup, overrides the config file' })
33
31
  .option('quiet', { short: 'q', description: 'Hide the header and the server/livereload info lines' })
34
32
 
@@ -43,21 +41,13 @@ try {
43
41
  const build = flags.build
44
42
  const defaultConfigPath = flags.config || positionals[0] || 'poops.json'
45
43
  const overridePort = flags.port
46
- const overrideLivereloadPort = flags['livereload-port']
47
44
  const overrideBaseURL = flags['base-url']
48
45
  const quiet = flags.quiet // hides the header and the address lines only — build logs, warnings and errors still print
49
46
 
50
47
  let configPath = path.join(cwd, defaultConfigPath)
51
48
  if (!pathExists(configPath)) configPath = path.join(cwd, '💩.json') // the canonical alternative config name
52
49
 
53
- async function resolveLiveReloadPort(config) {
54
- if (!config.livereload) return null
55
- let liveReloadPort = overrideLivereloadPort || config.livereload.port || 35729
56
- if (!overrideLivereloadPort) liveReloadPort = await getAvailablePort(liveReloadPort, liveReloadPort + 10)
57
- config.livereload_port = liveReloadPort
58
- }
59
-
60
- // The livereload server never fs-watches: watching the project meant every
50
+ // Nothing fs-watches on behalf of the browser: watching the project meant every
61
51
  // output file written during a build fired its own reload (dozens of browser
62
52
  // flickers per build, some mid-write). Instead the rebuild chains in
63
53
  // setupWatchers call reload() once their compile settles; the debounce folds
@@ -68,23 +58,23 @@ async function resolveLiveReloadPort(config) {
68
58
  // the first chain's reload. One save = one refresh, after dist is current.
69
59
  //
70
60
  // reload(file) collects paths over the debounce window. If everything in the
71
- // window is .css, each path is sent so the livereload client hot-swaps
61
+ // window is .css, the paths ride a `css` event so the client hot-swaps
72
62
  // stylesheets in place (no page reload, styles update without flicker);
73
- // anything else escalates to one full '/' refresh.
74
- let liveReloadServer = null
63
+ // anything else escalates to one full `reload`.
64
+ const reloadHub = createReloadHub()
75
65
  let reloadTimer = null
76
66
  const reloadPaths = new Set()
77
67
  function reload(file) {
78
- if (!liveReloadServer) return
68
+ if (!config.livereload) return
79
69
  reloadPaths.add(file || '/')
80
70
  clearTimeout(reloadTimer)
81
71
  reloadTimer = setTimeout(() => {
82
72
  const paths = [...reloadPaths]
83
73
  reloadPaths.clear()
84
74
  if (paths.every((p) => p.endsWith('.css'))) {
85
- paths.forEach((p) => liveReloadServer.refresh(p))
75
+ reloadHub.send('css', paths)
86
76
  } else {
87
- liveReloadServer.refresh('/')
77
+ reloadHub.send('reload', '/')
88
78
  }
89
79
  }, 500)
90
80
  }
@@ -104,13 +94,6 @@ function styleOutputs(styles) {
104
94
  // build and watch; see lib/exec.js. `hook(stage)` binds config + cwd here.
105
95
  const hook = (stage) => runExec(config, cwd, stage)
106
96
 
107
- function setupLiveReloadServer(config) {
108
- if (!config.livereload) return
109
- liveReloadServer = livereload.createServer({ port: config.livereload_port })
110
- if (!quiet) styledLog(`🔃 {dim}LiveReload :{/} ${liveReloadServer.config.port}`)
111
- console.log()
112
- }
113
-
114
97
  function setupWatchers(config, modules) {
115
98
  if (!config.watch) return
116
99
 
@@ -350,14 +333,29 @@ if (!pathExists(configPath)) {
350
333
  const config = JSON.parse(fs.readFileSync(configPath, 'utf-8'))
351
334
 
352
335
  // A typo'd top-level key ("stlyes") is otherwise silently ignored — warn, same
353
- // idea as validateExec for exec stages. `ssg` is the reactor alias; `pkg` feeds
354
- // banner templates; livereload_port/reactorData are set internally but tolerated
355
- // here in case a config hardcodes them.
356
- const KNOWN_CONFIG_KEYS = new Set(['watch', 'includePaths', 'scripts', 'styles', 'postcss', 'reactor', 'ssg', 'markup', 'copy', 'images', 'serve', 'livereload', 'exec', 'banner', 'pkg', 'livereload_port', 'reactorData'])
336
+ // idea as validateExec for exec stages. `pkg` feeds banner templates;
337
+ // reactorData is set internally but tolerated here in case a config hardcodes it.
338
+ const KNOWN_CONFIG_KEYS = new Set(['watch', 'includePaths', 'scripts', 'styles', 'postcss', 'reactor', 'markup', 'copy', 'images', 'serve', 'livereload', 'exec', 'banner', 'pkg', 'reactorData'])
339
+
340
+ // Keys that meant something in 1.x. They fall through the check above, and
341
+ // "unknown config key" would be accurate and useless — name the replacement.
342
+ const REMOVED_CONFIG_KEYS = { ssg: 'renamed to "reactor" in 2.0' }
343
+
357
344
  for (const key of Object.keys(config)) {
358
- if (!KNOWN_CONFIG_KEYS.has(key)) {
359
- log({ tag: 'info', warn: true, text: `Unknown config key "${key}" — ignored. Valid: ${[...KNOWN_CONFIG_KEYS].join(', ')}` })
345
+ if (KNOWN_CONFIG_KEYS.has(key)) continue
346
+ if (REMOVED_CONFIG_KEYS[key]) {
347
+ log({ tag: 'info', warn: true, text: `Config key "${key}" is ${REMOVED_CONFIG_KEYS[key]} — ignored.` })
348
+ continue
360
349
  }
350
+ log({ tag: 'info', warn: true, text: `Unknown config key "${key}" — ignored. Valid: ${[...KNOWN_CONFIG_KEYS].join(', ')}` })
351
+ }
352
+
353
+ // The reload channel is an endpoint on the static server, so livereload has
354
+ // nothing to attach to without `serve` — say so rather than idling in a mode
355
+ // that can never reach a browser.
356
+ if (!build && config.livereload && !config.serve) {
357
+ log({ tag: 'info', warn: true, text: 'Ignoring "livereload": it needs "serve" — the reload channel is served on the same port.' })
358
+ config.livereload = false
361
359
  }
362
360
 
363
361
  if (config.watch === true) {
@@ -372,13 +370,11 @@ if (config.includePaths) {
372
370
  config.includePaths = ['node_modules']
373
371
  }
374
372
 
375
- // Backwards compatibility: support "ssg" as alias for "reactor"
376
- if (!config.reactor && config.ssg) {
377
- config.reactor = config.ssg
378
- }
379
-
380
373
  if (overrideBaseURL && config.markup) {
381
- config.markup.baseURL = overrideBaseURL
374
+ // options is the canonical home, and it also wins over a config that still
375
+ // carries the deprecated markup-level key — the flag must override both
376
+ config.markup.options = config.markup.options || {}
377
+ config.markup.options.baseURL = overrideBaseURL
382
378
  }
383
379
 
384
380
  // poops-images resolves custom handlers/composites relative to the config file;
@@ -417,24 +413,33 @@ function getLocalIP() {
417
413
  }
418
414
 
419
415
  async function startServer() {
420
- await resolveLiveReloadPort(config)
421
416
  await poops() // Initial compilation before starting the server
422
417
 
423
- const base = config.serve.base && pathExists(cwd, config.serve.base)
424
- ? path.join(cwd, config.serve.base)
418
+ // Almost every config sets serve.base to the markup `out` it just built, so
419
+ // that is the default when it's unset. Falls back to cwd for a project with
420
+ // no markup — and an explicit serve.base still wins, including one pointing
421
+ // somewhere else entirely.
422
+ const serveBase = config.serve.base || (config.markup && config.markup.out)
423
+ const base = serveBase && pathExists(cwd, serveBase)
424
+ ? path.join(cwd, serveBase)
425
425
  : cwd
426
426
 
427
427
  let port = overridePort || config.serve.port || 4040
428
428
  if (!overridePort) port = await getAvailablePort(port, port + 10)
429
429
 
430
+ // The hub is only wired in when livereload is on; without it the server
431
+ // neither answers the reload endpoint nor injects the client into pages.
432
+ const handler = createStaticHandler(base, config.livereload ? reloadHub : null)
433
+
430
434
  // eslint-disable-next-line @stylistic/space-before-function-paren
431
- http.createServer(createStaticHandler(base)).listen(parseInt(port), '0.0.0.0', async () => {
435
+ http.createServer(handler).listen(parseInt(port), '0.0.0.0', async () => {
432
436
  if (!quiet) {
433
437
  console.log()
434
438
  styledLog(`🏠 {dim}Local server:{/} {underline|http://localhost:${port}}`)
435
439
  styledLog(`🛜 {dim} Network :{/} {underline|http://${getLocalIP()}:${port}}`)
440
+ if (config.livereload) styledLog('🔃 {dim}Live reload :{/} on')
441
+ console.log()
436
442
  }
437
- setupLiveReloadServer(config)
438
443
  })
439
444
  }
440
445
 
@@ -448,12 +453,6 @@ const die = (err) => {
448
453
  // Start the webserver
449
454
  if (!build && config.serve) {
450
455
  startServer().catch(die)
451
- } else if (!build && config.livereload) {
452
- // livereload without serve: still needs the livereload server
453
- resolveLiveReloadPort(config)
454
- .then(poops)
455
- .then(() => setupLiveReloadServer(config))
456
- .catch(die)
457
456
  } else {
458
457
  poops().catch(die)
459
458
  }