image-guard 5.2.6 → 5.3.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/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # Image Guard
2
2
 
3
- [![npm version](https://img.shields.io/npm/v/image-guard.svg)](https://www.npmjs.com/package/image-guard) [![Build status](https://github.com/j9t/image-guard/workflows/Tests/badge.svg)](https://github.com/j9t/image-guard/actions) [![Socket](https://badge.socket.dev/npm/package/image-guard)](https://socket.dev/npm/package/image-guard) [![GitHub Sponsors](https://badgen.net/static/Support/Open%20Source/cyan)](https://github.com/j9t/image-guard?sponsor=1)
3
+ [![npm version](https://img.shields.io/npm/v/image-guard.svg)](https://www.npmjs.com/package/image-guard) [![Build status](https://github.com/j9t/image-guard/workflows/Tests/badge.svg)](https://github.com/j9t/image-guard/actions) [![Socket](https://badge.socket.dev/npm/package/image-guard)](https://socket.dev/npm/package/image-guard) [![GitHub Sponsors](https://badgen.net/static/Support/Open%20Source/cyan)](https://github.com/sponsors/j9t)
4
4
 
5
5
  (This project was based on [sum.cumo’s imagemin-merlin](https://github.com/sumcumo/imagemin-merlin). [Changes are documented](https://github.com/sumcumo/imagemin-merlin/compare/master...j9t:master), and include this README. Image Guard supports additional file formats—WebP, AVIF, and HEIC/HEIF—, comes with improved code and documentation, and is being maintained. For this reason, it’s [not based on any Imagemin packages](https://meiert.com/blog/image-guard-4/) anymore.)
6
6
 
@@ -101,15 +101,19 @@ npm pkg set scripts.postprepare="grep -qxF './node_modules/.bin/image-guard --st
101
101
 
102
102
  (The `postprepare` script ensures that the hook is added to the repository whenever someone installs the package.)
103
103
 
104
+ If you give the hook options like `--ignore`, add them to the `postprepare` script as well—the hook command is the only place to configure Image Guard, and the next install rewrites that line.
105
+
104
106
  **Important:** When you commit images that have not yet been compressed, the automated compression process (triggered by the pre-commit hook) will modify those image files to reduce their size. As a result, after your initial commit attempt, you will see these images appear as changed files in Git. To include the optimized images in your repository, you need to stage and commit them again. In rare cases, if further compression is possible, you may need to repeat this process until no further changes are detected. This workflow is intentional and ensures that only optimally compressed images are committed. Many editors can display diffs for images, helping you review these changes.
105
107
 
106
108
  ### Parameters
107
109
 
110
+ Image Guard takes an optional path to the directory to process—`npx image-guard assets`—and works on the current directory when that path is omitted.
111
+
108
112
  * `--dry` allows you to run Image Guard in “dry mode.” All changes are shown in the terminal.
109
113
 
110
- * `--ignore` allows you to specify paths to be ignored (as in `--ignore=example,test`). Multiple paths must be separated by commas. The option supports glob patterns (e.g., `assets/**`, `**/*.png`); matching is case-insensitive and honors `.gitignore`.
114
+ * `--ignore` allows you to specify paths to be ignored (as in `--ignore=example,test`). Multiple paths must be separated by commas. Naming a directory excludes everything inside it. The option supports glob patterns (e.g., `assets/**`, `**/*.png`); matching is case-insensitive and honors `.gitignore`. Patterns are relative to the directory being processed—with `--staged`, to the repository root.
111
115
 
112
- * `--staged` (recommended with automated use) triggers a mode that watches PNG, JPG, GIF, WebP, and AVIF files (and HEIC/HEIF, when `--heic-to-avif` is used) in `git diff` and only processes those files—that approach makes Image Guard more efficient in operation.
116
+ * `--staged` (recommended with automated use) triggers a mode that watches PNG, JPG, GIF, WebP, and AVIF files (and HEIC/HEIF, when `--heic-to-avif` is used) from staged changes (`git diff --cached`) and only processes those files—that approach makes Image Guard more efficient in operation. Because the file set comes from Git, `--staged` can’t be combined with a path.
113
117
 
114
118
  * `--heic-to-avif` converts HEIC/HEIF files to AVIF. Because HEIC sources are already lossy-compressed (HEVC), the conversion uses lossy AVIF encoding (quality 80) to produce files smaller than the originals at near-identical visual quality. Original HEIC/HEIF files are deleted after conversion by default. (Note: The conversion maps Display P3 colors—common on iPhones—to sRGB, which may result in slightly less vivid colors.)
115
119
 
package/package.json CHANGED
@@ -6,7 +6,7 @@
6
6
  "imagemin-merlin": "bin/image-guard.sh"
7
7
  },
8
8
  "dependencies": {
9
- "globby": "^16.2.2",
9
+ "globby": "^16.2.3",
10
10
  "heic-decode": "^2.1.0",
11
11
  "sharp": "^0.35.3",
12
12
  "simple-git": "^3.36.0"
@@ -14,7 +14,7 @@
14
14
  "description": "Near-lossless image compressor that’s easy to use and easy to automate",
15
15
  "devDependencies": {
16
16
  "@eslint/js": "^10.0.1",
17
- "eslint": "^10.7.0"
17
+ "eslint": "^10.8.1"
18
18
  },
19
19
  "files": [
20
20
  "bin/image-guard.js",
@@ -50,8 +50,9 @@
50
50
  "scripts": {
51
51
  "lint": "eslint .",
52
52
  "lint:fix": "eslint . --fix",
53
+ "prepare": "git config core.hooksPath .githooks || true",
53
54
  "test": "node --test bin/image-guard.test.js"
54
55
  },
55
56
  "type": "module",
56
- "version": "5.2.6"
57
+ "version": "5.3.0"
57
58
  }
package/src/index.js CHANGED
@@ -3,6 +3,7 @@
3
3
  import { globby, convertPathToPattern } from 'globby'
4
4
  import simpleGit from 'simple-git'
5
5
  import { parseArgs, styleText } from 'node:util'
6
+ import fsSync from 'node:fs'
6
7
  import os from 'node:os'
7
8
  import path from 'node:path'
8
9
  import sharp from 'sharp'
@@ -23,7 +24,21 @@ export async function runImageGuard() {
23
24
  staged: { type: 'boolean', default: false },
24
25
  quiet: { type: 'boolean', default: false }
25
26
  }
26
- const { values: argv } = parseArgs({ options })
27
+ const { values: argv, positionals } = parseArgs({ options, allowPositionals: true })
28
+
29
+ if (positionals.length > 1) {
30
+ throw new Error(`Expected at most one path, got ${positionals.length}: ${positionals.join(', ')}`)
31
+ }
32
+
33
+ const dir = positionals[0] || '.'
34
+
35
+ if (positionals.length && argv.staged) {
36
+ throw new Error('`--staged` takes its files from Git, not from a path—pass one or the other.')
37
+ }
38
+
39
+ if (!fsSync.existsSync(dir) || !fsSync.statSync(dir).isDirectory()) {
40
+ throw new Error(`No such directory: ${dir}`)
41
+ }
27
42
 
28
43
  // Share status
29
44
  const summary = (run, includesConversion = false) => {
@@ -42,7 +57,8 @@ export async function runImageGuard() {
42
57
 
43
58
  const allTypes = argv['heic-to-avif'] ? [...fileTypes, ...convertTypes] : fileTypes
44
59
  if (!argv.quiet) {
45
- console.log(`(Search pattern: ${allTypes.join(', ')})\n`)
60
+ const where = dir === '.' ? '' : `, in ${dir}`
61
+ console.log(`(Search pattern: ${allTypes.join(', ')}${where})\n`)
46
62
  }
47
63
 
48
64
  let savedKB = 0
@@ -129,12 +145,15 @@ export async function runImageGuard() {
129
145
  return hadFailures
130
146
  }
131
147
 
148
+ // Plain patterns also get a “/**” variant so directories are excluded with
149
+ // their contents: “dir/” resolves only where globby can stat it, and a bare
150
+ // “dir” only covers what the walk descends into
132
151
  const getIgnorePatterns = (ignore) => {
133
152
  return (ignore || '')
134
153
  .split(',')
135
- .map(s => s.trim())
154
+ .map(s => s.trim().replace(/^!/, '').replace(/\/+$/, ''))
136
155
  .filter(Boolean)
137
- .map(p => (p.startsWith('!') ? p : `!${p}`))
156
+ .flatMap(p => (/[*?[\]{}]/.test(p) ? [`!${p}`] : [`!${p}`, `!${p}/**`]))
138
157
  }
139
158
 
140
159
  const getFilePattern = (ignore, types = fileTypes) => {
@@ -152,13 +171,18 @@ export async function runImageGuard() {
152
171
  return patterns
153
172
  }
154
173
 
174
+ // Globbing runs with `dir` as its base—so ignore patterns and `.gitignore`
175
+ // lookup are relative to the searched directory, not to the shell’s—and the
176
+ // results are rejoined for display and file access
155
177
  const findFiles = async (patterns, options = {}) => {
156
- return globby(patterns, {
178
+ const files = await globby(patterns, {
179
+ cwd: dir,
157
180
  gitignore: true,
158
181
  onlyFiles: true,
159
182
  caseSensitiveMatch: false,
160
183
  ...options
161
184
  })
185
+ return files.map(file => path.join(dir, file))
162
186
  }
163
187
 
164
188
  const filterStagedFiles = async (stagedFiles, types) => {