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.
@@ -1,51 +1,53 @@
1
- const fs = require('node:fs')
2
- const glob = require('glob')
3
- const path = require('node:path')
4
- const frontMatterCache = new Map()
1
+ import fs from 'node:fs'
2
+ import { hasMagic } from 'glob'
3
+ import path from 'node:path'
4
+ import yaml from 'yaml'
5
+ import { convertGlobToRegex } from 'book-of-spells'
5
6
 
6
- function pathExists() {
7
+ export function pathExists() {
7
8
  return fs.existsSync(path.join(...arguments))
8
9
  }
9
10
 
10
- function pathIsDirectory() {
11
+ export function pathIsDirectory() {
11
12
  return fs.lstatSync(path.join(...arguments)).isDirectory()
12
13
  }
13
14
 
14
- function mkDir(dirPath) {
15
+ export function mkDir(dirPath) {
15
16
  if (!fs.existsSync(dirPath)) fs.mkdirSync(dirPath, { recursive: true })
16
17
  }
17
18
 
18
- function mkPath(filePath) {
19
+ export function mkPath(filePath) {
19
20
  const dirPath = path.dirname(filePath)
20
21
  mkDir(dirPath)
21
22
  }
22
23
 
23
- function pathForFile(filePath) {
24
- return /\./.test(path.basename(filePath))
24
+ export function pathForFile(filePath) {
25
+ return path.extname(filePath) !== ''
25
26
  }
26
27
 
27
- function insertMinSuffix(filePath) {
28
+ export function insertMinSuffix(filePath) {
28
29
  const { name, ext } = path.parse(filePath)
29
30
  return path.join(path.dirname(filePath), `${name}.min${ext}`)
30
31
  }
31
32
 
32
- function buildStyleOutputFilePath(inputPath, outputPath) {
33
+ export function buildStyleOutputFilePath(inputPath, outputPath) {
33
34
  if (pathForFile(outputPath)) return outputPath
34
35
  const { name } = path.parse(inputPath)
35
36
  return path.join(path.join(outputPath, `${name}.css`))
36
37
  }
37
38
 
38
- function buildScriptOutputFilePath(inputPath, outputPath) {
39
+ export function buildScriptOutputFilePath(inputPath, outputPath) {
39
40
  if (pathForFile(outputPath)) return outputPath
40
41
  const { name, ext } = path.parse(inputPath)
41
- return path.join(path.join(outputPath, `${name}${ext.replace('t', 'j')}`))
42
+ const outExt = /\.(tsx?|jsx)$/i.test(ext) ? '.js' : ext
43
+ return path.join(outputPath, `${name}${outExt}`)
42
44
  }
43
45
 
44
- function fillBannerTemplate(template, packagesPath) {
46
+ export function fillBannerTemplate(template, packagesPath) {
45
47
  packagesPath = packagesPath || process.cwd()
46
48
  const packagesFilePath = path.join(packagesPath, 'package.json')
47
49
  if (!pathExists(packagesFilePath)) return template
48
- const pkg = require(packagesFilePath)
50
+ const pkg = JSON.parse(fs.readFileSync(packagesFilePath, 'utf-8'))
49
51
  const { name, version, homepage, description, license, author } = pkg
50
52
  const year = new Date().getFullYear()
51
53
 
@@ -59,96 +61,46 @@ function fillBannerTemplate(template, packagesPath) {
59
61
  .replace(/{{\s?year\s?}}/g, year)
60
62
  }
61
63
 
62
- function buildTime(start, end) {
64
+ export function buildTime(start, end) {
63
65
  const time = Math.round(end - start)
64
66
  if (time < 1000) return `${time}ms`
65
- if (time < 60 * 1000) return `${(time / 1000).toFixed(0)}s ${time % 1000}ms`
66
- return `${(time / 1000 / 60).toFixed(0)}m ${((time / 1000) % 60).toFixed(0)}s ${time % 1000}ms`
67
+ const seconds = Math.floor(time / 1000)
68
+ const ms = time % 1000
69
+ if (time < 60 * 1000) return `${seconds}s ${ms}ms`
70
+ const minutes = Math.floor(seconds / 60)
71
+ const remainingSeconds = seconds % 60
72
+ return `${minutes}m ${remainingSeconds}s ${ms}ms`
67
73
  }
68
74
 
69
- function fileSize(filePath) {
75
+ export function fileSize(filePath) {
70
76
  const stats = fs.statSync(filePath)
71
77
  const fileSizeInBytes = stats.size
72
78
  if (fileSizeInBytes < 1000) return `${fileSizeInBytes}B`
73
79
  if (fileSizeInBytes < 1000 * 1000) return `${(fileSizeInBytes / 1000).toFixed(0)}KB`
74
- if (fileSizeInBytes < 1000 * 1000 * 1000) return `${(fileSizeInBytes / 1000 / 1000).toFixed(0)}MB ${fileSizeInBytes % (1000 * 1000)}KB`
80
+ if (fileSizeInBytes < 1000 * 1000 * 1000) return `${Math.floor(fileSizeInBytes / 1000 / 1000)}MB ${Math.floor((fileSizeInBytes % (1000 * 1000)) / 1000)}KB`
75
81
  return fileSizeInBytes
76
82
  }
77
83
 
78
- function readJsonFile(filePath) {
84
+ export function readJsonFile(filePath) {
79
85
  return JSON.parse(fs.readFileSync(filePath, 'utf8'))
80
86
  }
81
87
 
82
- function readYamlFile(filePath) {
88
+ export function readYamlFile(filePath) {
83
89
  try {
84
- return require('yaml').parse(fs.readFileSync(filePath, 'utf8'))
90
+ return yaml.parse(fs.readFileSync(filePath, 'utf8'))
85
91
  } catch (e) {
86
92
  console.error(`Error reading YAML file at ${filePath}:`, e)
87
93
  return null
88
94
  }
89
95
  }
90
96
 
91
- function parseFrontMatter(filePath) {
92
- let stat
93
- try {
94
- stat = fs.statSync(filePath)
95
- } catch (e) {
96
- throw new Error(`Error stating file at ${filePath}: ${e.message}`)
97
- }
98
-
99
- const cached = frontMatterCache.get(filePath)
100
- if (cached && cached.mtimeMs === stat.mtimeMs && cached.size === stat.size) {
101
- return { frontMatter: { ...cached.value.frontMatter }, content: cached.value.content }
102
- }
103
-
104
- let content = ''
105
- try {
106
- content = fs.readFileSync(filePath, 'utf8')
107
- } catch (e) {
108
- throw new Error(`Error reading file at ${filePath}: ${e.message}`)
109
- }
110
-
111
- if (!content) {
112
- throw new Error(`File at ${filePath} is empty`)
113
- }
114
-
115
- const frontMatterRegex = /^\s*---\s*[\r\n]+([\s\S]*?)\s*---\s*[\r\n]+/
116
- const match = content.match(frontMatterRegex)
117
-
118
- if (!match) {
119
- const value = { frontMatter: {}, content }
120
- frontMatterCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, value })
121
- return { frontMatter: {}, content }
122
- }
123
-
124
- let frontMatter = {}
125
- try {
126
- frontMatter = require('yaml').parse(match[1])
127
- } catch (e) {
128
- throw new Error(`Error parsing front matter in file at ${filePath}: ${e.message}`)
129
- }
130
-
131
- const contentWithoutFrontMatter = content.slice(match[0].length)
132
- const value = { frontMatter, content: contentWithoutFrontMatter }
133
- frontMatterCache.set(filePath, { mtimeMs: stat.mtimeMs, size: stat.size, value })
134
- return { frontMatter: { ...frontMatter }, content: contentWithoutFrontMatter }
135
- }
136
-
137
- function clearFrontMatterCache(filePath) {
138
- if (!filePath) {
139
- frontMatterCache.clear()
140
- return
141
- }
142
- frontMatterCache.delete(filePath)
143
- }
144
-
145
- function readDataFile(filePath) {
97
+ export function readDataFile(filePath) {
146
98
  if (/(\.json)$/i.test(filePath)) return readJsonFile(filePath)
147
99
  if (/(\.ya?ml)$/i.test(filePath)) return readYamlFile(filePath)
148
100
  return fs.readFileSync(filePath, 'utf8')
149
101
  }
150
102
 
151
- function deleteDirectoryContents(directory) {
103
+ export function deleteDirectoryContents(directory) {
152
104
  if (!pathExists(directory)) return
153
105
  const files = fs.readdirSync(directory)
154
106
 
@@ -157,21 +109,19 @@ function deleteDirectoryContents(directory) {
157
109
  const stat = fs.statSync(filePath)
158
110
 
159
111
  if (stat.isDirectory()) {
160
- deleteDirectoryContents(filePath)
161
- fs.rmdirSync(filePath)
112
+ fs.rmSync(filePath, { recursive: true })
162
113
  } else {
163
114
  fs.unlinkSync(filePath)
164
115
  }
165
116
  }
166
117
  }
167
118
 
168
- function deleteDirectory(directory) {
119
+ export function deleteDirectory(directory) {
169
120
  if (!pathExists(directory)) return
170
- deleteDirectoryContents(directory)
171
- fs.rmdirSync(directory)
121
+ fs.rmSync(directory, { recursive: true })
172
122
  }
173
123
 
174
- function copyDirectory(src, dest) {
124
+ export function copyDirectory(src, dest) {
175
125
  if (!pathExists(src)) return
176
126
 
177
127
  if (!pathIsDirectory(src)) {
@@ -195,80 +145,13 @@ function copyDirectory(src, dest) {
195
145
  }
196
146
  }
197
147
 
198
- function convertGlobToRegex(glob) {
199
- let regexString = ''
200
- const charMap = {
201
- '*': '[^\\/\\\\]*',
202
- '?': '[^\\/\\\\]',
203
- '!': '^',
204
- '{': '(',
205
- '}': ')',
206
- ',': '|',
207
- '.': '\\.',
208
- '-': '\\-',
209
- // eslint-disable-next-line quote-props
210
- '$': '\\$',
211
- '+': '\\+',
212
- '/': '\\/',
213
- '\\': '\\\\'
214
- }
215
- let insideSquareBrackets = false
216
- let insideCurlyBraces = false
217
-
218
- for (let i = 0; i < glob.length; i++) {
219
- const char = glob[i]
220
- // eslint-disable-next-line no-prototype-builtins
221
- if (charMap.hasOwnProperty(char)) {
222
- let currentChar = charMap[char]
223
- switch (char) {
224
- case '{':
225
- insideCurlyBraces = true
226
- break
227
- case '}':
228
- insideCurlyBraces = false
229
- break
230
- case '!':
231
- if (!insideSquareBrackets) currentChar = '\\!' // Escape '!' outside square brackets
232
- break
233
- case ',':
234
- if (!insideCurlyBraces) currentChar = '\\,' // Escape ',' outside curly braces
235
- break
236
- case '-':
237
- if (insideSquareBrackets) currentChar = char // Don't escape '-' inside square brackets
238
- break
239
- }
240
- regexString += currentChar
241
- } else {
242
- switch (char) {
243
- case '[':
244
- insideSquareBrackets = true
245
- break
246
- case ']':
247
- insideSquareBrackets = false
248
- break
249
- }
250
- regexString += char
251
- }
252
- }
253
-
254
- let re = null
255
-
256
- try {
257
- re = new RegExp(regexString)
258
- } catch (e) {
259
- // Invalid regex
260
- }
261
-
262
- return re
263
- }
264
-
265
- function removeDirNavWildcards(filePath) {
148
+ export function stripDirNavSegments(filePath) {
266
149
  return path.normalize(filePath).replace(/(\.\.\/|\.\/|\/\.\.|\.\.\\|\.\\|\\\.\.)/g, '')
267
150
  }
268
151
 
269
- function pathContainsPathSegment(filePath, segment) {
270
- segment = removeDirNavWildcards(segment)
271
- if (glob.hasMagic(segment)) {
152
+ export function pathContainsPathSegment(filePath, segment) {
153
+ segment = stripDirNavSegments(segment)
154
+ if (hasMagic(segment)) {
272
155
  segment = convertGlobToRegex(segment)
273
156
  if (!segment) return false
274
157
  return segment.test(filePath)
@@ -277,7 +160,7 @@ function pathContainsPathSegment(filePath, segment) {
277
160
  }
278
161
  }
279
162
 
280
- function doesFileBelongToPath(filePath, configPaths) {
163
+ export function doesFileBelongToPath(filePath, configPaths) {
281
164
  if (!configPaths) return false
282
165
  if (!Array.isArray(configPaths)) configPaths = [configPaths]
283
166
  for (const configPath of configPaths) {
@@ -294,26 +177,3 @@ function doesFileBelongToPath(filePath, configPaths) {
294
177
  }
295
178
  return false
296
179
  }
297
-
298
- module.exports = {
299
- pathExists,
300
- pathIsDirectory,
301
- mkDir,
302
- mkPath,
303
- pathForFile,
304
- insertMinSuffix,
305
- buildStyleOutputFilePath,
306
- buildScriptOutputFilePath,
307
- fillBannerTemplate,
308
- buildTime,
309
- fileSize,
310
- readJsonFile,
311
- readYamlFile,
312
- readDataFile,
313
- deleteDirectoryContents,
314
- deleteDirectory,
315
- copyDirectory,
316
- doesFileBelongToPath,
317
- parseFrontMatter,
318
- clearFrontMatterCache
319
- }
@@ -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,8 +1,9 @@
1
1
  {
2
2
  "name": "poops",
3
3
  "description": "Straightforward, no-bullshit bundler for the web.",
4
- "version": "1.0.20",
4
+ "version": "1.2.0",
5
5
  "license": "MIT",
6
+ "type": "module",
6
7
  "main": "poops.js",
7
8
  "repository": {
8
9
  "type": "git",
@@ -14,7 +15,8 @@
14
15
  },
15
16
  "files": [
16
17
  "poops.js",
17
- "lib/"
18
+ "lib/",
19
+ "!lib/**/__tests__/"
18
20
  ],
19
21
  "author": "Stamat <@stamat> (http://stamat.info)",
20
22
  "homepage": "https://github.com/stamat/poops",
@@ -24,36 +26,60 @@
24
26
  "javascript",
25
27
  "sass",
26
28
  "nunjucks",
29
+ "liquid",
27
30
  "esbuild",
28
31
  "dart-sass",
32
+ "tailwindcss",
29
33
  "toolchain-script",
30
34
  "static-site-generator"
31
35
  ],
36
+ "peerDependencies": {
37
+ "postcss": "^8.0.0"
38
+ },
39
+ "peerDependenciesMeta": {
40
+ "postcss": {
41
+ "optional": true
42
+ }
43
+ },
32
44
  "scripts": {
33
45
  "build": "node ./poops.js -b",
34
- "lint": "eslint ./poops.js"
46
+ "lint": "eslint ./poops.js",
47
+ "test": "NODE_OPTIONS='--experimental-vm-modules' jest"
35
48
  },
36
49
  "dependencies": {
50
+ "argoyle": "^1.0.0",
51
+ "book-of-spells": "^1.0.32",
37
52
  "chokidar": "^3.5.3",
38
53
  "connect": "^3.7.0",
39
- "deepmerge": "^4.3.1",
54
+ "dayjs": "^1.11.19",
40
55
  "esbuild": "^0.25.0",
41
- "glob": "^10.3.1",
56
+ "glob": "^13.0.6",
57
+ "highlight.js": "^11.11.1",
58
+ "liquidjs": "^10.24.0",
42
59
  "livereload": "^0.9.3",
43
60
  "marked": "^9.0.3",
44
- "moment": "^2.29.4",
45
61
  "nunjucks": "^3.2.4",
46
62
  "portscanner": "^2.2.0",
63
+ "printstyle": "^1.0.0",
47
64
  "sass": "^1.63.4",
65
+ "sass-path-resolver": "^1.0.2",
66
+ "sass-token-importer": "^1.0.0",
48
67
  "serve-static": "^1.15.0",
49
68
  "yaml": "^2.3.1"
50
69
  },
51
70
  "devDependencies": {
52
- "eslint": "^8.42.0",
53
- "eslint-config-standard": "^17.1.0",
54
- "eslint-plugin-import": "^2.26.0",
55
- "eslint-plugin-n": "^16.0.0",
56
- "eslint-plugin-promise": "^6.1.1",
57
- "sulphuris": "^2.0.0"
71
+ "@jest/globals": "^30.2.0",
72
+ "@tailwindcss/postcss": "^4.2.1",
73
+ "eslint": "^9.39.3",
74
+ "jest": "^30.2.0",
75
+ "neostandard": "^0.12.2",
76
+ "postcss": "^8.5.8",
77
+ "react": "^19.2.4",
78
+ "react-dom": "^19.2.4",
79
+ "sulphuris": "^2.0.0",
80
+ "tailwindcss": "^4.2.1"
81
+ },
82
+ "overrides": {
83
+ "minimatch": ">=10.2.1"
58
84
  }
59
85
  }