bytecut 0.0.1 → 0.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/LICENSE +21 -0
- package/README.md +145 -0
- package/dist/{audit-HVW4EOLP.js → audit-LJOGSJS6.js} +12 -12
- package/dist/{check-YONIQTM7.js → check-MEBXJBNG.js} +11 -11
- package/dist/{chunk-YBEC5QGV.js → chunk-3PF6BBFK.js} +47 -20
- package/dist/{chunk-GMHOU4Y5.js → chunk-4FIB6VNC.js} +1 -1
- package/dist/{chunk-N536JFSF.js → chunk-5JEA233Z.js} +15 -9
- package/dist/{chunk-7IECTPJB.js → chunk-7DHE25UE.js} +1 -1
- package/dist/{chunk-XFJ3HW4X.js → chunk-CSFBJ3UH.js} +21 -10
- package/dist/{chunk-TWI2OZJZ.js → chunk-FY53B4NB.js} +10 -3
- package/dist/{chunk-CEZK3RQJ.js → chunk-HLBNRB3S.js} +6 -6
- package/dist/{chunk-LHTI3JMC.js → chunk-IZ4JEL3Q.js} +1 -1
- package/dist/{chunk-4XOB3C34.js → chunk-KN5QOB7R.js} +4 -4
- package/dist/{chunk-G53LNJ5O.js → chunk-LJWOPIM6.js} +1 -1
- package/dist/{chunk-J2XJO4JZ.js → chunk-MV6J66AP.js} +1 -1
- package/dist/{chunk-326CQQWX.js → chunk-PQRBLHCC.js} +1 -1
- package/dist/{chunk-WE7AEDVI.js → chunk-QOG2F4SF.js} +2 -2
- package/dist/{chunk-NAKV7HS5.js → chunk-RZ2V4PCS.js} +22 -10
- package/dist/{chunk-M6ZDSATP.js → chunk-S3FQYUTI.js} +1 -1
- package/dist/{chunk-5R7LR5BB.js → chunk-TCQFQ354.js} +4 -4
- package/dist/{chunk-U4KJMKI5.js → chunk-WZ6MMRNB.js} +12 -3
- package/dist/cli.js +23 -23
- package/dist/{compare-6PCE6533.js → compare-MPPGTUC5.js} +8 -8
- package/dist/{doctor-R226JU6P.js → doctor-PWCUEBD5.js} +5 -5
- package/dist/{explain-SGN5BOR7.js → explain-LKOB7FRT.js} +15 -13
- package/dist/{init-WHWUANOB.js → init-CWDWVJ55.js} +4 -4
- package/dist/{optimize-UXM6JEDA.js → optimize-SLLQGDDX.js} +12 -12
- package/dist/{snippet-2WVGMCNV.js → snippet-IIPVF6PS.js} +8 -8
- package/dist/{tui-J4Y44IL7.js → tui-ZAEXBY4L.js} +11 -11
- package/package.json +1 -1
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 anupjon
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
ADDED
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
# bytecut
|
|
2
|
+
|
|
3
|
+
Audit and optimize the images and videos in a web project, from the terminal.
|
|
4
|
+
|
|
5
|
+
```sh
|
|
6
|
+
npx bytecut
|
|
7
|
+
```
|
|
8
|
+
|
|
9
|
+
With no configuration, bytecut detects your framework (Next.js, Astro, Vite or a static
|
|
10
|
+
site), scans the asset folders, shows what is too heavy and how much you would save, and
|
|
11
|
+
optimizes on Enter. Your original files are never modified: outputs go to a separate folder
|
|
12
|
+
that mirrors your source tree, and that folder is meant to be committed.
|
|
13
|
+
|
|
14
|
+
## What you get
|
|
15
|
+
|
|
16
|
+
By default each image becomes **one JPEG**, with the longest side capped at 1920 px (a
|
|
17
|
+
landscape image is 1920 wide, a portrait one is 1920 tall, nothing is ever upscaled). Metadata
|
|
18
|
+
and GPS are stripped, orientation is applied and the colour profile is kept.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
public/img/hero.jpg → public/_bytecut/img/hero-1920.jpg
|
|
22
|
+
public/_bytecut/bytecut-manifest.json
|
|
23
|
+
```
|
|
24
|
+
|
|
25
|
+
- **Transparency is preserved.** A source that really uses alpha gets a lossless PNG instead of
|
|
26
|
+
a JPEG, and so does flat art such as icons and logos.
|
|
27
|
+
- **WebP and AVIF are opt-in** (see [Configuration](#configuration)).
|
|
28
|
+
- **A file is only written if it is smaller** than the source; otherwise it is reported as
|
|
29
|
+
"already optimal".
|
|
30
|
+
- **Videos and animated GIFs** become AV1 and H.264 MP4 plus a poster, using your own
|
|
31
|
+
`ffmpeg` (see [Video](#video)).
|
|
32
|
+
|
|
33
|
+
Outputs are flat: an image at the top of your static folder lands directly in `_bytecut/`, and
|
|
34
|
+
one in a subfolder keeps that subfolder (`public/img/hero.jpg` → `_bytecut/img/hero-1920.jpg`).
|
|
35
|
+
There is no extra folder per image.
|
|
36
|
+
|
|
37
|
+
Re-running is cheap: unchanged sources are skipped by content hash, and the manifest is
|
|
38
|
+
byte-identical when nothing changed, so it diffs cleanly in pull requests.
|
|
39
|
+
|
|
40
|
+
## Commands
|
|
41
|
+
|
|
42
|
+
| Command | What it does |
|
|
43
|
+
| ----------------------------- | ------------------------------------------------------------------------- |
|
|
44
|
+
| `bytecut` | Interactive session on a TTY; prints the audit elsewhere |
|
|
45
|
+
| `bytecut audit` | Report what is too heavy, with estimated savings |
|
|
46
|
+
| `bytecut optimize [paths] -y` | Write optimized files. Without `-y` it prints the plan and writes nothing |
|
|
47
|
+
| `bytecut snippet <file>` | Print the `<picture>` / `<video>` markup (HTML or JSX) for a file |
|
|
48
|
+
| `bytecut compare <file>` | Serve a before/after page on `127.0.0.1` (random port, private path) |
|
|
49
|
+
| `bytecut explain <file>` | Show which rule chose a file's preset and what it resolves to |
|
|
50
|
+
| `bytecut init` | Write a minimal `bytecut.config.json` with only non-default choices |
|
|
51
|
+
| `bytecut check` | CI mode: exit 1 if a finding is an error, 2 if it could not run |
|
|
52
|
+
| `bytecut doctor` | Report Node, sharp, ffmpeg, the scorer, and what is degraded |
|
|
53
|
+
|
|
54
|
+
Useful flags: `--dry-run --json` prints the plan as JSON and `--plan <file>` executes a saved
|
|
55
|
+
one; `--out-dir`, `--preset`, `--force`, `--placeholder dominant-color|lqip|blurhash|none`.
|
|
56
|
+
|
|
57
|
+
In the interactive session: `space` toggles a file, `enter` reviews and runs, `p` changes the
|
|
58
|
+
preset, `o` the output folder, `d` details, `esc` cancels, `?` help. Type `/` for commands
|
|
59
|
+
(`/snippet @file`, `/compare @file`, `/save`) and `@` to mention a file.
|
|
60
|
+
|
|
61
|
+
## Configuration
|
|
62
|
+
|
|
63
|
+
Everything is optional. Config is only needed to make a choice stick. Put it in
|
|
64
|
+
`bytecut.config.json` (or `.ts`, `.mjs`, `.toml`); unknown keys are errors.
|
|
65
|
+
|
|
66
|
+
```json
|
|
67
|
+
{
|
|
68
|
+
"presets": {
|
|
69
|
+
"photo": { "formats": ["jpg", "webp", "avif"] },
|
|
70
|
+
"gallery": {
|
|
71
|
+
"extends": "photo",
|
|
72
|
+
"maxEdge": 1280,
|
|
73
|
+
"ssimulacra2": 80,
|
|
74
|
+
"maxSize": "150 KB",
|
|
75
|
+
"floor": 65
|
|
76
|
+
},
|
|
77
|
+
"loop": { "extends": "background-video", "vmaf": 92 }
|
|
78
|
+
},
|
|
79
|
+
"assets": { "public/gallery/**": "gallery", "public/loops/*.mp4": "loop" },
|
|
80
|
+
"budgets": { "maxFileSize": "250 KB", "maxTotalSize": "5 MB" }
|
|
81
|
+
}
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
- **Presets**: built-in `photo`, `hero`, `icon`, `background-video`, `content-video` and
|
|
85
|
+
`gif-to-video`. Add your own with `extends`; the first matching `assets` glob wins.
|
|
86
|
+
- **Size**: `maxEdge` caps the longest side; `widths` lists widths instead (a responsive set).
|
|
87
|
+
- **Formats**: `jpg` (default), `png`, `webp`, `avif`. A `<picture>` lists AVIF, then WebP, and
|
|
88
|
+
the JPEG is the `<img>`.
|
|
89
|
+
- **Quality**: `target` (`low`, `balanced`, `high`, `lossless`), or `quality` per format.
|
|
90
|
+
- **Perceptual targets**: see below.
|
|
91
|
+
|
|
92
|
+
## Quality targets
|
|
93
|
+
|
|
94
|
+
Instead of a fixed quality you can ask for a score. bytecut then finds the **smallest** output
|
|
95
|
+
that still meets it:
|
|
96
|
+
|
|
97
|
+
- `ssimulacra2: 80` (images and video) searches quality on a representative crop, then
|
|
98
|
+
confirms on the full-size output.
|
|
99
|
+
- `maxSize` with a required `floor` lowers quality, then steps the width down, to fit a size
|
|
100
|
+
cap. A constraint that cannot be met writes nothing and fails the run; it is never silently
|
|
101
|
+
exceeded.
|
|
102
|
+
- `vmaf: 92` (video) uses ffmpeg's `libvmaf`. Without it, the preset's fixed quality is used
|
|
103
|
+
and a warning says so.
|
|
104
|
+
|
|
105
|
+
SSIMULACRA2 runs as WebAssembly in worker threads, so no native binary is needed. A native
|
|
106
|
+
`ssimulacra2` is used if one is found (`--ssimulacra2`, `BYTECUT_SSIMULACRA2`,
|
|
107
|
+
`ssimulacra2Path`, or `PATH`), as a speed-up only.
|
|
108
|
+
|
|
109
|
+
## Video
|
|
110
|
+
|
|
111
|
+
bytecut uses **your own** `ffmpeg` and `ffprobe` (`--ffmpeg`, `BYTECUT_FFMPEG`, `ffmpegPath`, or
|
|
112
|
+
`PATH`). It never downloads or bundles one. Without ffmpeg images work fully and videos are
|
|
113
|
+
listed as "needs ffmpeg" with the install command for your OS. Without an AV1 encoder you still
|
|
114
|
+
get the H.264 file and the poster. `bytecut doctor` shows exactly what is available.
|
|
115
|
+
|
|
116
|
+
## CI
|
|
117
|
+
|
|
118
|
+
```sh
|
|
119
|
+
npx bytecut check # exit 1 on an error finding
|
|
120
|
+
npx bytecut check --fail-on warn # stricter
|
|
121
|
+
npx bytecut check --changed origin/main # only files changed since a ref
|
|
122
|
+
npx bytecut check --report md # a Markdown report for a PR comment
|
|
123
|
+
```
|
|
124
|
+
|
|
125
|
+
`project/over-budget` is an error by default; other rules are promoted or silenced under
|
|
126
|
+
`audit.rules`.
|
|
127
|
+
|
|
128
|
+
## Guarantees
|
|
129
|
+
|
|
130
|
+
- Sources are read-only. bytecut only writes inside the output folder (and the config file,
|
|
131
|
+
and only when you ask it to), atomically, so a cancelled run never leaves a half-written
|
|
132
|
+
file.
|
|
133
|
+
- It only deletes files listed in its manifest, and never without confirmation or an explicit flag.
|
|
134
|
+
- No network access at runtime: no telemetry, no update checks, no downloads.
|
|
135
|
+
- Same input, same settings and same tool versions give the same bytes where the encoder allows.
|
|
136
|
+
|
|
137
|
+
## Requirements
|
|
138
|
+
|
|
139
|
+
Node.js 22 or newer. Images use [sharp](https://sharp.pixelplumbing.com). Developed and tested
|
|
140
|
+
on Linux; macOS and Windows have not been verified yet.
|
|
141
|
+
|
|
142
|
+
## Licence
|
|
143
|
+
|
|
144
|
+
MIT. The bundled SSIMULACRA2 build (`bytecut-ssimulacra2`) is a WebAssembly compilation of
|
|
145
|
+
[rust-av/ssimulacra2](https://github.com/rust-av/ssimulacra2) and is BSD-2-Clause.
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
} from "./chunk-LTWUPQDT.js";
|
|
5
5
|
import {
|
|
6
6
|
audit
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5JEA233Z.js";
|
|
8
8
|
import {
|
|
9
9
|
formatAuditReport
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HLBNRB3S.js";
|
|
11
11
|
import "./chunk-3DBJKHOD.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-TCQFQ354.js";
|
|
13
13
|
import {
|
|
14
14
|
sharpImageEngine
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-CSFBJ3UH.js";
|
|
16
|
+
import "./chunk-RZ2V4PCS.js";
|
|
17
17
|
import {
|
|
18
18
|
configArg,
|
|
19
19
|
createVideoEngine,
|
|
@@ -21,15 +21,15 @@ import {
|
|
|
21
21
|
ffmpegInstallText,
|
|
22
22
|
ffmpegStatus,
|
|
23
23
|
noAssetsMessage
|
|
24
|
-
} from "./chunk-
|
|
25
|
-
import "./chunk-
|
|
26
|
-
import "./chunk-
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
24
|
+
} from "./chunk-KN5QOB7R.js";
|
|
25
|
+
import "./chunk-S3FQYUTI.js";
|
|
26
|
+
import "./chunk-QOG2F4SF.js";
|
|
27
|
+
import "./chunk-3PF6BBFK.js";
|
|
28
|
+
import "./chunk-IZ4JEL3Q.js";
|
|
29
29
|
import {
|
|
30
30
|
guarded
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-MV6J66AP.js";
|
|
32
|
+
import "./chunk-FY53B4NB.js";
|
|
33
33
|
|
|
34
34
|
// src/cli/commands/audit.ts
|
|
35
35
|
import { defineCommand } from "citty";
|
|
@@ -4,16 +4,16 @@ import {
|
|
|
4
4
|
} from "./chunk-LTWUPQDT.js";
|
|
5
5
|
import {
|
|
6
6
|
audit
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-5JEA233Z.js";
|
|
8
8
|
import {
|
|
9
9
|
formatAuditReport
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HLBNRB3S.js";
|
|
11
11
|
import "./chunk-3DBJKHOD.js";
|
|
12
|
-
import "./chunk-
|
|
12
|
+
import "./chunk-TCQFQ354.js";
|
|
13
13
|
import {
|
|
14
14
|
sharpImageEngine
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-CSFBJ3UH.js";
|
|
16
|
+
import "./chunk-RZ2V4PCS.js";
|
|
17
17
|
import {
|
|
18
18
|
configArg,
|
|
19
19
|
createVideoEngine,
|
|
@@ -21,22 +21,22 @@ import {
|
|
|
21
21
|
ffmpegInstallText,
|
|
22
22
|
ffmpegStatus,
|
|
23
23
|
noAssetsMessage
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-KN5QOB7R.js";
|
|
25
25
|
import {
|
|
26
26
|
run
|
|
27
|
-
} from "./chunk-
|
|
27
|
+
} from "./chunk-S3FQYUTI.js";
|
|
28
28
|
import {
|
|
29
29
|
toPosix
|
|
30
|
-
} from "./chunk-
|
|
31
|
-
import "./chunk-
|
|
32
|
-
import "./chunk-
|
|
30
|
+
} from "./chunk-QOG2F4SF.js";
|
|
31
|
+
import "./chunk-3PF6BBFK.js";
|
|
32
|
+
import "./chunk-IZ4JEL3Q.js";
|
|
33
33
|
import {
|
|
34
34
|
BytecutError,
|
|
35
35
|
CONFIG_FILES,
|
|
36
36
|
formatBytes,
|
|
37
37
|
percentChange,
|
|
38
38
|
plural
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-FY53B4NB.js";
|
|
40
40
|
|
|
41
41
|
// src/cli/commands/check.ts
|
|
42
42
|
import { defineCommand } from "citty";
|
|
@@ -4,14 +4,35 @@ import {
|
|
|
4
4
|
detectProject,
|
|
5
5
|
hint,
|
|
6
6
|
matchesGlob
|
|
7
|
-
} from "./chunk-
|
|
7
|
+
} from "./chunk-FY53B4NB.js";
|
|
8
|
+
|
|
9
|
+
// src/core/presets/photo.ts
|
|
10
|
+
var OTHER_ENCODERS = {
|
|
11
|
+
// PNG has no quality; `effort` is libpng's/oxipng-style effort (1–10).
|
|
12
|
+
png: { quality: 100, effort: 7 }
|
|
13
|
+
};
|
|
14
|
+
var PHOTO_PRESET = {
|
|
15
|
+
name: "photo",
|
|
16
|
+
widths: [],
|
|
17
|
+
maxEdge: 1920,
|
|
18
|
+
formats: ["jpg"],
|
|
19
|
+
encoders: {
|
|
20
|
+
...OTHER_ENCODERS,
|
|
21
|
+
jpg: { quality: 80, effort: 0 },
|
|
22
|
+
avif: { quality: 60, effort: 4 },
|
|
23
|
+
webp: { quality: 78, effort: 4 }
|
|
24
|
+
}
|
|
25
|
+
};
|
|
8
26
|
|
|
9
27
|
// src/core/presets/hero.ts
|
|
10
28
|
var HERO_PRESET = {
|
|
11
29
|
name: "hero",
|
|
12
|
-
widths: [
|
|
13
|
-
|
|
30
|
+
widths: [],
|
|
31
|
+
maxEdge: 1920,
|
|
32
|
+
formats: ["jpg"],
|
|
14
33
|
encoders: {
|
|
34
|
+
...OTHER_ENCODERS,
|
|
35
|
+
jpg: { quality: 85, effort: 0 },
|
|
15
36
|
avif: { quality: 65, effort: 4 },
|
|
16
37
|
webp: { quality: 82, effort: 4 }
|
|
17
38
|
},
|
|
@@ -22,25 +43,16 @@ var HERO_PRESET = {
|
|
|
22
43
|
var ICON_PRESET = {
|
|
23
44
|
name: "icon",
|
|
24
45
|
widths: [64, 128, 256, 512],
|
|
25
|
-
formats: ["
|
|
46
|
+
formats: ["png"],
|
|
26
47
|
encoders: {
|
|
48
|
+
...OTHER_ENCODERS,
|
|
49
|
+
jpg: { quality: 90, effort: 0 },
|
|
27
50
|
avif: { quality: 80, effort: 4 },
|
|
28
51
|
webp: { quality: 90, effort: 4 }
|
|
29
52
|
},
|
|
30
53
|
lossless: true
|
|
31
54
|
};
|
|
32
55
|
|
|
33
|
-
// src/core/presets/photo.ts
|
|
34
|
-
var PHOTO_PRESET = {
|
|
35
|
-
name: "photo",
|
|
36
|
-
widths: [640, 1280, 1920],
|
|
37
|
-
formats: ["avif", "webp"],
|
|
38
|
-
encoders: {
|
|
39
|
-
avif: { quality: 60, effort: 4 },
|
|
40
|
-
webp: { quality: 78, effort: 4 }
|
|
41
|
-
}
|
|
42
|
-
};
|
|
43
|
-
|
|
44
56
|
// src/core/presets/index.ts
|
|
45
57
|
var IMAGE_PRESETS = {
|
|
46
58
|
photo: PHOTO_PRESET,
|
|
@@ -122,13 +134,14 @@ var BUILTIN_POLICY = {
|
|
|
122
134
|
|
|
123
135
|
// src/core/config/presets.ts
|
|
124
136
|
var IMAGE_TARGETS = {
|
|
125
|
-
low: { avif: 45, webp: 65 },
|
|
126
|
-
balanced: { avif: 60, webp: 78 },
|
|
127
|
-
high: { avif: 75, webp: 88 }
|
|
137
|
+
low: { jpg: 65, avif: 45, webp: 65 },
|
|
138
|
+
balanced: { jpg: 80, avif: 60, webp: 78 },
|
|
139
|
+
high: { jpg: 90, avif: 75, webp: 88 }
|
|
128
140
|
};
|
|
129
141
|
var VIDEO_TARGET_CRF_OFFSET = { low: 4, balanced: 0, high: -4 };
|
|
130
142
|
var IMAGE_ONLY = [
|
|
131
143
|
"widths",
|
|
144
|
+
"maxEdge",
|
|
132
145
|
"formats",
|
|
133
146
|
"quality",
|
|
134
147
|
"effort",
|
|
@@ -156,15 +169,29 @@ function applyImage(name, base, def) {
|
|
|
156
169
|
name,
|
|
157
170
|
widths: [...base.widths],
|
|
158
171
|
formats: [...base.formats],
|
|
159
|
-
encoders: {
|
|
172
|
+
encoders: {
|
|
173
|
+
jpg: { ...base.encoders.jpg },
|
|
174
|
+
png: { ...base.encoders.png },
|
|
175
|
+
avif: { ...base.encoders.avif },
|
|
176
|
+
webp: { ...base.encoders.webp }
|
|
177
|
+
}
|
|
160
178
|
};
|
|
161
179
|
if (def.target === "lossless") next.lossless = true;
|
|
162
180
|
else if (def.target) {
|
|
181
|
+
next.encoders.jpg.quality = IMAGE_TARGETS[def.target].jpg;
|
|
163
182
|
next.encoders.avif.quality = IMAGE_TARGETS[def.target].avif;
|
|
164
183
|
next.encoders.webp.quality = IMAGE_TARGETS[def.target].webp;
|
|
165
184
|
}
|
|
166
|
-
if (def.widths)
|
|
185
|
+
if (def.widths) {
|
|
186
|
+
next.widths = [...new Set(def.widths)].sort((a, b) => a - b);
|
|
187
|
+
delete next.maxEdge;
|
|
188
|
+
}
|
|
189
|
+
if (def.maxEdge !== void 0) {
|
|
190
|
+
next.maxEdge = def.maxEdge;
|
|
191
|
+
next.widths = [];
|
|
192
|
+
}
|
|
167
193
|
if (def.formats) next.formats = [...new Set(def.formats)];
|
|
194
|
+
if (def.quality?.jpg !== void 0) next.encoders.jpg.quality = def.quality.jpg;
|
|
168
195
|
if (def.quality?.avif !== void 0) next.encoders.avif.quality = def.quality.avif;
|
|
169
196
|
if (def.quality?.webp !== void 0) next.encoders.webp.quality = def.quality.webp;
|
|
170
197
|
if (def.effort?.avif !== void 0) next.encoders.avif.effort = def.effort.avif;
|
|
@@ -6,38 +6,38 @@ import {
|
|
|
6
6
|
sortFindings,
|
|
7
7
|
walkFiles,
|
|
8
8
|
withVideos
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-HLBNRB3S.js";
|
|
10
10
|
import {
|
|
11
11
|
MANIFEST_FILE,
|
|
12
12
|
parseManifest
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-TCQFQ354.js";
|
|
14
14
|
import {
|
|
15
15
|
ANIMATED_GIF_REASON,
|
|
16
16
|
buildPlan,
|
|
17
17
|
classify,
|
|
18
18
|
classifyVideo,
|
|
19
19
|
sha256
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-RZ2V4PCS.js";
|
|
21
21
|
import {
|
|
22
22
|
isFastStart,
|
|
23
23
|
toPosix
|
|
24
|
-
} from "./chunk-
|
|
24
|
+
} from "./chunk-QOG2F4SF.js";
|
|
25
25
|
import {
|
|
26
26
|
BUILTIN_POLICY,
|
|
27
27
|
getImagePreset,
|
|
28
28
|
pickAv1Encoder,
|
|
29
29
|
rootPreset
|
|
30
|
-
} from "./chunk-
|
|
30
|
+
} from "./chunk-3PF6BBFK.js";
|
|
31
31
|
import {
|
|
32
32
|
SafeFs
|
|
33
|
-
} from "./chunk-
|
|
33
|
+
} from "./chunk-IZ4JEL3Q.js";
|
|
34
34
|
import {
|
|
35
35
|
ALL_RULES,
|
|
36
36
|
checkCache,
|
|
37
37
|
checkVideoCache,
|
|
38
38
|
matchesGlob,
|
|
39
39
|
savingOf
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-FY53B4NB.js";
|
|
41
41
|
|
|
42
42
|
// src/core/commands/audit.ts
|
|
43
43
|
import { stat as stat2 } from "fs/promises";
|
|
@@ -66,8 +66,14 @@ async function estimateItem(item, readData, deps) {
|
|
|
66
66
|
const outs = item.outputs.filter((o) => o.format === format).sort((a, b) => a.width - b.width);
|
|
67
67
|
const largest = outs[outs.length - 1];
|
|
68
68
|
const within = outs.filter((o) => o.width <= TRIAL_MAX_WIDTH);
|
|
69
|
-
const trial = within.length > 0 ? within[within.length - 1] : outs[0];
|
|
70
|
-
const key = estimateKey(
|
|
69
|
+
const trial = within.length > 0 ? within[within.length - 1] : { ...outs[0], width: TRIAL_MAX_WIDTH };
|
|
70
|
+
const key = estimateKey(
|
|
71
|
+
item.sourceSha256,
|
|
72
|
+
item.preset,
|
|
73
|
+
trial,
|
|
74
|
+
trial.width,
|
|
75
|
+
encoders[format] ?? format
|
|
76
|
+
);
|
|
71
77
|
let bytes = deps.cache.getEstimate(key)?.bytes;
|
|
72
78
|
if (bytes === void 0) {
|
|
73
79
|
const encoded = await deps.engine.encode(await readData(), trial);
|
|
@@ -131,6 +131,8 @@ async function sampleColors(input) {
|
|
|
131
131
|
function encoderNames() {
|
|
132
132
|
const v = sharp.versions;
|
|
133
133
|
return {
|
|
134
|
+
jpg: `mozjpeg ${v.mozjpeg ?? "unknown"}`,
|
|
135
|
+
png: `libpng ${v.png ?? "unknown"}`,
|
|
134
136
|
avif: `libaom-av1 ${v.aom ?? "unknown"}`,
|
|
135
137
|
webp: `libwebp ${v.webp ?? "unknown"}`
|
|
136
138
|
};
|
|
@@ -155,6 +157,18 @@ async function makePlaceholder(input, kind) {
|
|
|
155
157
|
}
|
|
156
158
|
}
|
|
157
159
|
}
|
|
160
|
+
function encodeAs(image, r) {
|
|
161
|
+
switch (r.format) {
|
|
162
|
+
case "jpg":
|
|
163
|
+
return image.jpeg({ quality: r.quality, mozjpeg: true });
|
|
164
|
+
case "png":
|
|
165
|
+
return image.png({ compressionLevel: 9, effort: Math.max(1, Math.min(10, r.effort)) });
|
|
166
|
+
case "avif":
|
|
167
|
+
return image.avif({ quality: r.quality, effort: r.effort, lossless: r.lossless });
|
|
168
|
+
case "webp":
|
|
169
|
+
return image.webp({ quality: r.quality, effort: r.effort, lossless: r.lossless });
|
|
170
|
+
}
|
|
171
|
+
}
|
|
158
172
|
var sharpImageEngine = {
|
|
159
173
|
encoders: encoderNames,
|
|
160
174
|
placeholder: makePlaceholder,
|
|
@@ -211,7 +225,12 @@ var sharpImageEngine = {
|
|
|
211
225
|
const raw = sharp(image.data, {
|
|
212
226
|
raw: { width: image.width, height: image.height, channels: image.channels }
|
|
213
227
|
});
|
|
214
|
-
const encoded =
|
|
228
|
+
const encoded = encodeAs(raw, {
|
|
229
|
+
format: request.format,
|
|
230
|
+
quality: request.quality,
|
|
231
|
+
effort: request.effort,
|
|
232
|
+
lossless: false
|
|
233
|
+
});
|
|
215
234
|
const { data, info } = await encoded.toBuffer({ resolveWithObject: true });
|
|
216
235
|
return {
|
|
217
236
|
data,
|
|
@@ -224,15 +243,7 @@ var sharpImageEngine = {
|
|
|
224
243
|
const meta = await sharp(input).metadata();
|
|
225
244
|
let image = sharp(input).rotate().resize({ width: request.width, withoutEnlargement: true });
|
|
226
245
|
image = meta.space === "cmyk" ? image.toColourspace("srgb") : image.keepIccProfile();
|
|
227
|
-
image = request
|
|
228
|
-
quality: request.quality,
|
|
229
|
-
effort: request.effort,
|
|
230
|
-
lossless: request.lossless
|
|
231
|
-
}) : image.webp({
|
|
232
|
-
quality: request.quality,
|
|
233
|
-
effort: request.effort,
|
|
234
|
-
lossless: request.lossless
|
|
235
|
-
});
|
|
246
|
+
image = encodeAs(image, request);
|
|
236
247
|
const { data, info } = await image.toBuffer({ resolveWithObject: true });
|
|
237
248
|
return {
|
|
238
249
|
data,
|
|
@@ -307,7 +307,7 @@ var noModernFormat = {
|
|
|
307
307
|
ruleId: ID8,
|
|
308
308
|
severity: "warn",
|
|
309
309
|
source: image.source,
|
|
310
|
-
message: `${format.toUpperCase()} source has no up-to-date
|
|
310
|
+
message: `${format.toUpperCase()} source has no up-to-date optimized outputs`,
|
|
311
311
|
current: image.bytes,
|
|
312
312
|
estimated: image.estimate.best.bytes,
|
|
313
313
|
savingsBytes: saving
|
|
@@ -1006,7 +1006,7 @@ var FRAMEWORKS = ["next", "astro", "vite", "static"];
|
|
|
1006
1006
|
var rejected = (what, why = M9) => z.unknown().optional().superRefine((value, ctx) => {
|
|
1007
1007
|
if (value !== void 0) ctx.addIssue({ code: "custom", message: `${what} ${why}` });
|
|
1008
1008
|
});
|
|
1009
|
-
var qualityByFormat = z.object({ avif: quality.optional(), webp: quality.optional() }).strict();
|
|
1009
|
+
var qualityByFormat = z.object({ jpg: quality.optional(), avif: quality.optional(), webp: quality.optional() }).strict();
|
|
1010
1010
|
var effortByFormat = z.object({
|
|
1011
1011
|
avif: z.number().int().min(0).max(9).optional(),
|
|
1012
1012
|
webp: z.number().int().min(0).max(6).optional()
|
|
@@ -1020,7 +1020,8 @@ var presetSchema = z.object({
|
|
|
1020
1020
|
target: z.enum(TARGETS).optional(),
|
|
1021
1021
|
// images
|
|
1022
1022
|
widths: z.array(z.number().int().min(16).max(16384)).min(1).optional(),
|
|
1023
|
-
|
|
1023
|
+
maxEdge: z.number().int().min(16).max(16384).optional(),
|
|
1024
|
+
formats: z.array(z.enum(["jpg", "png", "webp", "avif"])).min(1).optional(),
|
|
1024
1025
|
quality: qualityByFormat.optional(),
|
|
1025
1026
|
effort: effortByFormat.optional(),
|
|
1026
1027
|
lossless: z.boolean().optional(),
|
|
@@ -1040,6 +1041,12 @@ var presetSchema = z.object({
|
|
|
1040
1041
|
raw: rejected("`raw`", "is not supported; use `target`, `quality` or `crf`")
|
|
1041
1042
|
}).strict().superRefine((preset, ctx) => {
|
|
1042
1043
|
const say = (path, message) => ctx.addIssue({ code: "custom", path: [path], message });
|
|
1044
|
+
if (preset.maxEdge !== void 0 && preset.widths !== void 0) {
|
|
1045
|
+
say(
|
|
1046
|
+
"maxEdge",
|
|
1047
|
+
"cannot be combined with `widths`: one caps the longest side, the other lists widths"
|
|
1048
|
+
);
|
|
1049
|
+
}
|
|
1043
1050
|
if (preset.vmaf !== void 0 && preset.ssimulacra2 !== void 0) {
|
|
1044
1051
|
say("vmaf", "cannot be combined with `ssimulacra2`: a preset searches for one metric");
|
|
1045
1052
|
}
|
|
@@ -7,7 +7,7 @@ import {
|
|
|
7
7
|
MANIFEST_VERSION,
|
|
8
8
|
parseManifest,
|
|
9
9
|
serializeManifest
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-TCQFQ354.js";
|
|
11
11
|
import {
|
|
12
12
|
ANIMATED_GIF_REASON,
|
|
13
13
|
DEFAULT_PUBLIC_PATH,
|
|
@@ -16,20 +16,20 @@ import {
|
|
|
16
16
|
classify,
|
|
17
17
|
sha256,
|
|
18
18
|
sha256File
|
|
19
|
-
} from "./chunk-
|
|
19
|
+
} from "./chunk-RZ2V4PCS.js";
|
|
20
20
|
import {
|
|
21
21
|
AbortedError,
|
|
22
22
|
outputWidth
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-QOG2F4SF.js";
|
|
24
24
|
import {
|
|
25
25
|
BUILTIN_POLICY,
|
|
26
26
|
IMAGE_PRESETS,
|
|
27
27
|
getImagePreset,
|
|
28
28
|
pickAv1Encoder
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-3PF6BBFK.js";
|
|
30
30
|
import {
|
|
31
31
|
SafeFs
|
|
32
|
-
} from "./chunk-
|
|
32
|
+
} from "./chunk-IZ4JEL3Q.js";
|
|
33
33
|
import {
|
|
34
34
|
BytecutError,
|
|
35
35
|
PROJECT_RULES,
|
|
@@ -39,7 +39,7 @@ import {
|
|
|
39
39
|
percentChange,
|
|
40
40
|
plural,
|
|
41
41
|
recordedByPlannedPath
|
|
42
|
-
} from "./chunk-
|
|
42
|
+
} from "./chunk-FY53B4NB.js";
|
|
43
43
|
|
|
44
44
|
// src/core/commands/optimize.ts
|
|
45
45
|
import { stat as stat3 } from "fs/promises";
|
|
@@ -3,19 +3,19 @@ import {
|
|
|
3
3
|
isFile,
|
|
4
4
|
readOsReleaseId,
|
|
5
5
|
run
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-S3FQYUTI.js";
|
|
7
7
|
import {
|
|
8
8
|
createFfmpegVideoEngine
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-QOG2F4SF.js";
|
|
10
10
|
import {
|
|
11
11
|
installHint,
|
|
12
12
|
resolveBinary,
|
|
13
13
|
resolveConfig
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-3PF6BBFK.js";
|
|
15
15
|
import {
|
|
16
16
|
BytecutError,
|
|
17
17
|
loadConfig
|
|
18
|
-
} from "./chunk-
|
|
18
|
+
} from "./chunk-FY53B4NB.js";
|
|
19
19
|
|
|
20
20
|
// src/cli/shared.ts
|
|
21
21
|
import { existsSync } from "fs";
|
|
@@ -3,10 +3,10 @@ import {
|
|
|
3
3
|
ffmpegDirOf,
|
|
4
4
|
probeFfmpeg,
|
|
5
5
|
resolveBinary
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-3PF6BBFK.js";
|
|
7
7
|
import {
|
|
8
8
|
BytecutError
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-FY53B4NB.js";
|
|
10
10
|
|
|
11
11
|
// src/core/posix.ts
|
|
12
12
|
import { sep } from "path";
|
|
@@ -2,14 +2,14 @@
|
|
|
2
2
|
import {
|
|
3
3
|
outputWidth,
|
|
4
4
|
toPosix
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-QOG2F4SF.js";
|
|
6
6
|
import {
|
|
7
7
|
BUILTIN_POLICY,
|
|
8
8
|
getVideoPreset
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-3PF6BBFK.js";
|
|
10
10
|
import {
|
|
11
11
|
BytecutError
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-FY53B4NB.js";
|
|
13
13
|
|
|
14
14
|
// src/core/classify/classify.ts
|
|
15
15
|
var FLAT_COLOR_LIMIT = 64;
|
|
@@ -82,6 +82,18 @@ function targetWidths(presetWidths, sourceWidth) {
|
|
|
82
82
|
if (sourceWidth < largest && !widths.includes(sourceWidth)) widths.push(sourceWidth);
|
|
83
83
|
return [...new Set(widths)].sort((a, b) => a - b);
|
|
84
84
|
}
|
|
85
|
+
function outputWidths(preset, sourceWidth, sourceHeight) {
|
|
86
|
+
if (preset.maxEdge === void 0) return targetWidths(preset.widths, sourceWidth);
|
|
87
|
+
const longest = Math.max(sourceWidth, sourceHeight);
|
|
88
|
+
if (longest <= preset.maxEdge) return [sourceWidth];
|
|
89
|
+
return [Math.max(1, Math.floor(sourceWidth * preset.maxEdge / longest))];
|
|
90
|
+
}
|
|
91
|
+
function formatsFor(preset, analysis, confidence) {
|
|
92
|
+
const ambiguousPng = analysis.format === "png" && confidence === "low";
|
|
93
|
+
const needsPng = analysis.alphaUsed || preset.lossless === true || ambiguousPng;
|
|
94
|
+
const out = preset.formats.map((f) => f === "jpg" && needsPng ? "png" : f);
|
|
95
|
+
return [...new Set(out)];
|
|
96
|
+
}
|
|
85
97
|
function mirroredDir(source, staticRoot) {
|
|
86
98
|
const dir = posix.dirname(source);
|
|
87
99
|
if (staticRoot !== "." && staticRoot !== "") {
|
|
@@ -146,10 +158,10 @@ async function buildPlan(options) {
|
|
|
146
158
|
const dir = mirroredDir(source, staticRoot);
|
|
147
159
|
const name = posix.basename(source, posix.extname(source));
|
|
148
160
|
const outputs = [];
|
|
149
|
-
for (const width of
|
|
150
|
-
for (const format of preset.
|
|
151
|
-
const lossless = preset.lossless ?? (analysis.format === "png" && format === "webp");
|
|
152
|
-
const path = posix.join(dir,
|
|
161
|
+
for (const width of outputWidths(preset, analysis.width, analysis.height)) {
|
|
162
|
+
for (const format of formatsFor(preset, analysis, choice.confidence)) {
|
|
163
|
+
const lossless = format === "png" || (preset.lossless ?? (analysis.format === "png" && format === "webp"));
|
|
164
|
+
const path = posix.join(dir, `${name}-${width}.${format}`);
|
|
153
165
|
const owner = claimed.get(path);
|
|
154
166
|
if (owner !== void 0) {
|
|
155
167
|
throw new BytecutError(
|
|
@@ -248,7 +260,7 @@ function plannedVideoOutputs(source, staticRoot, preset, probe) {
|
|
|
248
260
|
for (const codec of preset.codecs) {
|
|
249
261
|
outputs.push({
|
|
250
262
|
role: "video",
|
|
251
|
-
path: posix2.join(dir,
|
|
263
|
+
path: posix2.join(dir, `${name}-${width}.${codec}.mp4`),
|
|
252
264
|
codec,
|
|
253
265
|
width,
|
|
254
266
|
crf: preset.crf[codec],
|
|
@@ -262,7 +274,7 @@ function plannedVideoOutputs(source, staticRoot, preset, probe) {
|
|
|
262
274
|
}
|
|
263
275
|
outputs.push({
|
|
264
276
|
role: "poster",
|
|
265
|
-
path: posix2.join(dir,
|
|
277
|
+
path: posix2.join(dir, `${name}-poster.avif`),
|
|
266
278
|
format: "avif",
|
|
267
279
|
width,
|
|
268
280
|
quality: preset.poster.quality,
|
|
@@ -359,7 +371,7 @@ export {
|
|
|
359
371
|
classify,
|
|
360
372
|
sha256,
|
|
361
373
|
sha256File,
|
|
362
|
-
|
|
374
|
+
outputWidths,
|
|
363
375
|
mirroredDir,
|
|
364
376
|
DEFAULT_PUBLIC_PATH,
|
|
365
377
|
ANIMATED_GIF_REASON,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
BytecutError
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-FY53B4NB.js";
|
|
5
5
|
|
|
6
6
|
// src/core/plan/schema.ts
|
|
7
7
|
import { z } from "zod";
|
|
@@ -11,11 +11,11 @@ var safeRelativePath = z.string().min(1).refine(
|
|
|
11
11
|
);
|
|
12
12
|
var outputSchema = z.object({
|
|
13
13
|
path: safeRelativePath,
|
|
14
|
-
format: z.enum(["
|
|
14
|
+
format: z.enum(["jpg", "png", "webp", "avif"]),
|
|
15
15
|
width: z.number().int().positive(),
|
|
16
16
|
lossless: z.boolean(),
|
|
17
17
|
quality: z.number().int().min(1).max(100),
|
|
18
|
-
effort: z.number().int().min(0).max(
|
|
18
|
+
effort: z.number().int().min(0).max(10),
|
|
19
19
|
search: z.object({
|
|
20
20
|
metric: z.literal("ssimulacra2"),
|
|
21
21
|
target: z.number().min(1).max(100),
|
|
@@ -139,7 +139,7 @@ import { z as z2 } from "zod";
|
|
|
139
139
|
var outputSchema2 = z2.object({
|
|
140
140
|
path: z2.string().min(1),
|
|
141
141
|
url: z2.string(),
|
|
142
|
-
format: z2.enum(["
|
|
142
|
+
format: z2.enum(["jpg", "png", "webp", "avif"]),
|
|
143
143
|
width: z2.number().int().nonnegative(),
|
|
144
144
|
height: z2.number().int().nonnegative(),
|
|
145
145
|
bytes: z2.number().int().nonnegative(),
|
|
@@ -111,11 +111,12 @@ function imageElement(entry, target, options) {
|
|
|
111
111
|
]
|
|
112
112
|
});
|
|
113
113
|
}
|
|
114
|
-
const
|
|
115
|
-
const
|
|
114
|
+
const compatible = ofFormat("jpg").length > 0 ? ofFormat("jpg") : ofFormat("png");
|
|
115
|
+
const fallbackSet = compatible.length > 0 ? compatible : ofFormat("webp");
|
|
116
|
+
const fallback = fallbackSet[fallbackSet.length - 1];
|
|
116
117
|
if (!fallback) {
|
|
117
118
|
hints.push(
|
|
118
|
-
sources.length === 0 ? "No optimized outputs were written (they were not smaller than the source), so this uses the original file." : "No WebP output exists, so the <img> falls back to the original file."
|
|
119
|
+
sources.length === 0 ? "No optimized outputs were written (they were not smaller than the source), so this uses the original file." : "No JPEG, PNG or WebP output exists, so the <img> falls back to the original file."
|
|
119
120
|
);
|
|
120
121
|
}
|
|
121
122
|
const priority = options.priority ?? entry.priority === true;
|
|
@@ -125,6 +126,14 @@ function imageElement(entry, target, options) {
|
|
|
125
126
|
const height = fallback?.height ?? entry.height;
|
|
126
127
|
const img = [
|
|
127
128
|
{ name: "src", value: src },
|
|
129
|
+
// Several JPEG/PNG widths and no <picture> to carry them: the <img> carries the set.
|
|
130
|
+
...compatible.length > 1 ? [
|
|
131
|
+
{
|
|
132
|
+
name: "srcset",
|
|
133
|
+
value: compatible.map((o) => `${encodeUrl(o.url)} ${o.width}w`).join(", ")
|
|
134
|
+
},
|
|
135
|
+
{ name: "sizes", value: options.sizes ?? "100vw" }
|
|
136
|
+
] : [],
|
|
128
137
|
{ name: "width", value: String(width) },
|
|
129
138
|
{ name: "height", value: String(height) },
|
|
130
139
|
{ name: "alt", value: options.alt ?? "" }
|
package/dist/cli.js
CHANGED
|
@@ -2,25 +2,25 @@
|
|
|
2
2
|
import {
|
|
3
3
|
collectDoctorReport,
|
|
4
4
|
formatDoctorReport
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PQRBLHCC.js";
|
|
6
6
|
import {
|
|
7
7
|
svgoEngine
|
|
8
8
|
} from "./chunk-LTWUPQDT.js";
|
|
9
9
|
import {
|
|
10
10
|
audit
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-5JEA233Z.js";
|
|
12
12
|
import {
|
|
13
13
|
package_default
|
|
14
|
-
} from "./chunk-
|
|
14
|
+
} from "./chunk-LJWOPIM6.js";
|
|
15
15
|
import {
|
|
16
16
|
formatAuditReport
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-HLBNRB3S.js";
|
|
18
18
|
import "./chunk-3DBJKHOD.js";
|
|
19
|
-
import "./chunk-
|
|
19
|
+
import "./chunk-TCQFQ354.js";
|
|
20
20
|
import {
|
|
21
21
|
sharpImageEngine
|
|
22
|
-
} from "./chunk-
|
|
23
|
-
import "./chunk-
|
|
22
|
+
} from "./chunk-CSFBJ3UH.js";
|
|
23
|
+
import "./chunk-RZ2V4PCS.js";
|
|
24
24
|
import {
|
|
25
25
|
createVideoEngine,
|
|
26
26
|
discover,
|
|
@@ -28,17 +28,17 @@ import {
|
|
|
28
28
|
ffmpegStatus,
|
|
29
29
|
makeScorerFactory,
|
|
30
30
|
noAssetsMessage
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-KN5QOB7R.js";
|
|
32
32
|
import {
|
|
33
33
|
doctorDeps
|
|
34
|
-
} from "./chunk-
|
|
35
|
-
import "./chunk-
|
|
36
|
-
import "./chunk-
|
|
37
|
-
import "./chunk-
|
|
34
|
+
} from "./chunk-S3FQYUTI.js";
|
|
35
|
+
import "./chunk-QOG2F4SF.js";
|
|
36
|
+
import "./chunk-3PF6BBFK.js";
|
|
37
|
+
import "./chunk-IZ4JEL3Q.js";
|
|
38
38
|
import {
|
|
39
39
|
guarded
|
|
40
|
-
} from "./chunk-
|
|
41
|
-
import "./chunk-
|
|
40
|
+
} from "./chunk-MV6J66AP.js";
|
|
41
|
+
import "./chunk-FY53B4NB.js";
|
|
42
42
|
|
|
43
43
|
// src/cli/index.ts
|
|
44
44
|
import { defineCommand, runMain } from "citty";
|
|
@@ -57,7 +57,7 @@ async function runDefault() {
|
|
|
57
57
|
return;
|
|
58
58
|
}
|
|
59
59
|
if (isInteractive()) {
|
|
60
|
-
const { runTui } = await import("./tui-
|
|
60
|
+
const { runTui } = await import("./tui-ZAEXBY4L.js");
|
|
61
61
|
await runTui({
|
|
62
62
|
projectRoot: found.projectRoot,
|
|
63
63
|
project: found.project,
|
|
@@ -125,14 +125,14 @@ var main = defineCommand({
|
|
|
125
125
|
await runDefault();
|
|
126
126
|
}),
|
|
127
127
|
subCommands: {
|
|
128
|
-
audit: () => import("./audit-
|
|
129
|
-
optimize: () => import("./optimize-
|
|
130
|
-
snippet: () => import("./snippet-
|
|
131
|
-
compare: () => import("./compare-
|
|
132
|
-
explain: () => import("./explain-
|
|
133
|
-
init: () => import("./init-
|
|
134
|
-
check: () => import("./check-
|
|
135
|
-
doctor: () => import("./doctor-
|
|
128
|
+
audit: () => import("./audit-LJOGSJS6.js").then((m) => m.default),
|
|
129
|
+
optimize: () => import("./optimize-SLLQGDDX.js").then((m) => m.default),
|
|
130
|
+
snippet: () => import("./snippet-IIPVF6PS.js").then((m) => m.default),
|
|
131
|
+
compare: () => import("./compare-MPPGTUC5.js").then((m) => m.default),
|
|
132
|
+
explain: () => import("./explain-LKOB7FRT.js").then((m) => m.default),
|
|
133
|
+
init: () => import("./init-CWDWVJ55.js").then((m) => m.default),
|
|
134
|
+
check: () => import("./check-MEBXJBNG.js").then((m) => m.default),
|
|
135
|
+
doctor: () => import("./doctor-PWCUEBD5.js").then((m) => m.default)
|
|
136
136
|
}
|
|
137
137
|
});
|
|
138
138
|
await runMain(main);
|
|
@@ -1,26 +1,26 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
startCompareServer
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-7DHE25UE.js";
|
|
5
5
|
import {
|
|
6
6
|
MANIFEST_FILE,
|
|
7
7
|
parseManifest
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-TCQFQ354.js";
|
|
9
9
|
import {
|
|
10
10
|
configArg,
|
|
11
11
|
discover
|
|
12
|
-
} from "./chunk-
|
|
13
|
-
import "./chunk-
|
|
12
|
+
} from "./chunk-KN5QOB7R.js";
|
|
13
|
+
import "./chunk-S3FQYUTI.js";
|
|
14
14
|
import {
|
|
15
15
|
toPosix
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-QOG2F4SF.js";
|
|
17
|
+
import "./chunk-3PF6BBFK.js";
|
|
18
18
|
import {
|
|
19
19
|
guarded
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-MV6J66AP.js";
|
|
21
21
|
import {
|
|
22
22
|
BytecutError
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-FY53B4NB.js";
|
|
24
24
|
|
|
25
25
|
// src/cli/commands/compare.ts
|
|
26
26
|
import { readFile } from "fs/promises";
|
|
@@ -2,24 +2,24 @@
|
|
|
2
2
|
import {
|
|
3
3
|
collectDoctorReport,
|
|
4
4
|
formatDoctorReport
|
|
5
|
-
} from "./chunk-
|
|
5
|
+
} from "./chunk-PQRBLHCC.js";
|
|
6
6
|
import {
|
|
7
7
|
doctorDeps
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-S3FQYUTI.js";
|
|
9
9
|
import {
|
|
10
10
|
resolveConfig
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-3PF6BBFK.js";
|
|
12
12
|
import {
|
|
13
13
|
isGitIgnored
|
|
14
14
|
} from "./chunk-DJ45NQZD.js";
|
|
15
15
|
import {
|
|
16
16
|
guarded
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-MV6J66AP.js";
|
|
18
18
|
import {
|
|
19
19
|
BytecutError,
|
|
20
20
|
detectProject,
|
|
21
21
|
loadConfig
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-FY53B4NB.js";
|
|
23
23
|
|
|
24
24
|
// src/cli/commands/doctor.ts
|
|
25
25
|
import { defineCommand } from "citty";
|
|
@@ -1,34 +1,34 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
sharpImageEngine
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-CSFBJ3UH.js";
|
|
5
5
|
import {
|
|
6
6
|
classify,
|
|
7
7
|
classifyVideo,
|
|
8
8
|
mirroredDir,
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
} from "./chunk-
|
|
9
|
+
outputWidths,
|
|
10
|
+
plannedVideoOutputs
|
|
11
|
+
} from "./chunk-RZ2V4PCS.js";
|
|
12
12
|
import {
|
|
13
13
|
configArg,
|
|
14
14
|
createVideoEngine,
|
|
15
15
|
discover
|
|
16
|
-
} from "./chunk-
|
|
17
|
-
import "./chunk-
|
|
16
|
+
} from "./chunk-KN5QOB7R.js";
|
|
17
|
+
import "./chunk-S3FQYUTI.js";
|
|
18
18
|
import {
|
|
19
19
|
toPosix
|
|
20
|
-
} from "./chunk-
|
|
20
|
+
} from "./chunk-QOG2F4SF.js";
|
|
21
21
|
import {
|
|
22
22
|
getVideoPreset
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-3PF6BBFK.js";
|
|
24
24
|
import {
|
|
25
25
|
guarded
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-MV6J66AP.js";
|
|
27
27
|
import {
|
|
28
28
|
BytecutError,
|
|
29
29
|
formatBytes,
|
|
30
30
|
matchesGlob
|
|
31
|
-
} from "./chunk-
|
|
31
|
+
} from "./chunk-FY53B4NB.js";
|
|
32
32
|
|
|
33
33
|
// src/cli/commands/explain.ts
|
|
34
34
|
import { stat } from "fs/promises";
|
|
@@ -122,8 +122,8 @@ async function explainFile(options) {
|
|
|
122
122
|
const resolved2 = preset2 ? catalog.image[preset2] : null;
|
|
123
123
|
const dir = mirroredDir(source, config.project.staticRoot);
|
|
124
124
|
const name = posix.basename(source, posix.extname(source));
|
|
125
|
-
const outputs = resolved2 ?
|
|
126
|
-
(w) => resolved2.formats.map((f) => posix.join(dir,
|
|
125
|
+
const outputs = resolved2 ? outputWidths(resolved2, analysis.width, analysis.height).flatMap(
|
|
126
|
+
(w) => resolved2.formats.map((f) => posix.join(dir, `${name}-${w}.${f}`))
|
|
127
127
|
) : [];
|
|
128
128
|
return {
|
|
129
129
|
...base,
|
|
@@ -168,7 +168,9 @@ function formatExplanation(e) {
|
|
|
168
168
|
}
|
|
169
169
|
const r = e.resolved;
|
|
170
170
|
if (r && "widths" in r) {
|
|
171
|
-
lines.push(
|
|
171
|
+
lines.push(
|
|
172
|
+
r.maxEdge !== void 0 ? ` size: longest side up to ${r.maxEdge}px (width if landscape, height if portrait; never above the source)` : ` widths: ${r.widths.join(", ")} (never above the source)`
|
|
173
|
+
);
|
|
172
174
|
lines.push(` formats: ${r.formats.join(", ")}`);
|
|
173
175
|
for (const f of r.formats) {
|
|
174
176
|
const lossless = r.lossless ? ", lossless" : "";
|
|
@@ -6,20 +6,20 @@ import {
|
|
|
6
6
|
applyChoices,
|
|
7
7
|
isEmpty,
|
|
8
8
|
renderConfig
|
|
9
|
-
} from "./chunk-
|
|
9
|
+
} from "./chunk-4FIB6VNC.js";
|
|
10
10
|
import {
|
|
11
11
|
writeConfigFile
|
|
12
|
-
} from "./chunk-
|
|
12
|
+
} from "./chunk-IZ4JEL3Q.js";
|
|
13
13
|
import {
|
|
14
14
|
guarded
|
|
15
|
-
} from "./chunk-
|
|
15
|
+
} from "./chunk-MV6J66AP.js";
|
|
16
16
|
import {
|
|
17
17
|
BytecutError,
|
|
18
18
|
FRAMEWORKS,
|
|
19
19
|
PLACEHOLDERS,
|
|
20
20
|
detectProject,
|
|
21
21
|
findConfigFiles
|
|
22
|
-
} from "./chunk-
|
|
22
|
+
} from "./chunk-FY53B4NB.js";
|
|
23
23
|
|
|
24
24
|
// src/cli/commands/init.ts
|
|
25
25
|
import { relative } from "path";
|
|
@@ -1,42 +1,42 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
package_default
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-LJWOPIM6.js";
|
|
5
5
|
import {
|
|
6
6
|
formatFindings,
|
|
7
7
|
formatPlan,
|
|
8
8
|
formatSummary,
|
|
9
9
|
optimize
|
|
10
|
-
} from "./chunk-
|
|
10
|
+
} from "./chunk-HLBNRB3S.js";
|
|
11
11
|
import "./chunk-3DBJKHOD.js";
|
|
12
12
|
import {
|
|
13
13
|
isPlaceholderKind,
|
|
14
14
|
parsePlan,
|
|
15
15
|
serializePlan
|
|
16
|
-
} from "./chunk-
|
|
16
|
+
} from "./chunk-TCQFQ354.js";
|
|
17
17
|
import {
|
|
18
18
|
sharpImageEngine
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-CSFBJ3UH.js";
|
|
20
|
+
import "./chunk-RZ2V4PCS.js";
|
|
21
21
|
import {
|
|
22
22
|
configArg,
|
|
23
23
|
createVideoEngine,
|
|
24
24
|
ffmpegInstallText,
|
|
25
25
|
makeScorerFactory
|
|
26
|
-
} from "./chunk-
|
|
27
|
-
import "./chunk-
|
|
28
|
-
import "./chunk-
|
|
26
|
+
} from "./chunk-KN5QOB7R.js";
|
|
27
|
+
import "./chunk-S3FQYUTI.js";
|
|
28
|
+
import "./chunk-QOG2F4SF.js";
|
|
29
29
|
import {
|
|
30
30
|
resolveConfig
|
|
31
|
-
} from "./chunk-
|
|
32
|
-
import "./chunk-
|
|
31
|
+
} from "./chunk-3PF6BBFK.js";
|
|
32
|
+
import "./chunk-IZ4JEL3Q.js";
|
|
33
33
|
import {
|
|
34
34
|
guarded
|
|
35
|
-
} from "./chunk-
|
|
35
|
+
} from "./chunk-MV6J66AP.js";
|
|
36
36
|
import {
|
|
37
37
|
BytecutError,
|
|
38
38
|
loadConfig
|
|
39
|
-
} from "./chunk-
|
|
39
|
+
} from "./chunk-FY53B4NB.js";
|
|
40
40
|
|
|
41
41
|
// src/cli/commands/optimize.ts
|
|
42
42
|
import { existsSync } from "fs";
|
|
@@ -3,27 +3,27 @@ import {
|
|
|
3
3
|
buildSnippet,
|
|
4
4
|
originalUrl,
|
|
5
5
|
snippetFormatFor
|
|
6
|
-
} from "./chunk-
|
|
6
|
+
} from "./chunk-WZ6MMRNB.js";
|
|
7
7
|
import "./chunk-3DBJKHOD.js";
|
|
8
8
|
import {
|
|
9
9
|
MANIFEST_FILE,
|
|
10
10
|
parseManifest
|
|
11
|
-
} from "./chunk-
|
|
11
|
+
} from "./chunk-TCQFQ354.js";
|
|
12
12
|
import {
|
|
13
13
|
configArg,
|
|
14
14
|
discover
|
|
15
|
-
} from "./chunk-
|
|
16
|
-
import "./chunk-
|
|
15
|
+
} from "./chunk-KN5QOB7R.js";
|
|
16
|
+
import "./chunk-S3FQYUTI.js";
|
|
17
17
|
import {
|
|
18
18
|
toPosix
|
|
19
|
-
} from "./chunk-
|
|
20
|
-
import "./chunk-
|
|
19
|
+
} from "./chunk-QOG2F4SF.js";
|
|
20
|
+
import "./chunk-3PF6BBFK.js";
|
|
21
21
|
import {
|
|
22
22
|
guarded
|
|
23
|
-
} from "./chunk-
|
|
23
|
+
} from "./chunk-MV6J66AP.js";
|
|
24
24
|
import {
|
|
25
25
|
BytecutError
|
|
26
|
-
} from "./chunk-
|
|
26
|
+
} from "./chunk-FY53B4NB.js";
|
|
27
27
|
|
|
28
28
|
// src/cli/commands/snippet.ts
|
|
29
29
|
import { readFile } from "fs/promises";
|
|
@@ -1,43 +1,43 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import {
|
|
3
3
|
audit
|
|
4
|
-
} from "./chunk-
|
|
4
|
+
} from "./chunk-5JEA233Z.js";
|
|
5
5
|
import {
|
|
6
6
|
formatSummary,
|
|
7
7
|
optimize
|
|
8
|
-
} from "./chunk-
|
|
8
|
+
} from "./chunk-HLBNRB3S.js";
|
|
9
9
|
import {
|
|
10
10
|
buildSnippet,
|
|
11
11
|
originalUrl,
|
|
12
12
|
snippetFormatFor
|
|
13
|
-
} from "./chunk-
|
|
13
|
+
} from "./chunk-WZ6MMRNB.js";
|
|
14
14
|
import "./chunk-3DBJKHOD.js";
|
|
15
15
|
import {
|
|
16
16
|
startCompareServer
|
|
17
|
-
} from "./chunk-
|
|
17
|
+
} from "./chunk-7DHE25UE.js";
|
|
18
18
|
import {
|
|
19
19
|
MANIFEST_FILE,
|
|
20
20
|
parseManifest
|
|
21
|
-
} from "./chunk-
|
|
22
|
-
import "./chunk-
|
|
21
|
+
} from "./chunk-TCQFQ354.js";
|
|
22
|
+
import "./chunk-RZ2V4PCS.js";
|
|
23
23
|
import {
|
|
24
24
|
toPosix
|
|
25
|
-
} from "./chunk-
|
|
25
|
+
} from "./chunk-QOG2F4SF.js";
|
|
26
26
|
import {
|
|
27
27
|
resolveConfig,
|
|
28
28
|
resolveConfigData
|
|
29
|
-
} from "./chunk-
|
|
29
|
+
} from "./chunk-3PF6BBFK.js";
|
|
30
30
|
import {
|
|
31
31
|
applyChoices,
|
|
32
32
|
canRewrite,
|
|
33
33
|
isEmpty,
|
|
34
34
|
renderChoicesSnippet,
|
|
35
35
|
renderConfig
|
|
36
|
-
} from "./chunk-
|
|
36
|
+
} from "./chunk-4FIB6VNC.js";
|
|
37
37
|
import {
|
|
38
38
|
SafeFs,
|
|
39
39
|
writeConfigFile
|
|
40
|
-
} from "./chunk-
|
|
40
|
+
} from "./chunk-IZ4JEL3Q.js";
|
|
41
41
|
import {
|
|
42
42
|
ALL_RULES,
|
|
43
43
|
BytecutError,
|
|
@@ -46,7 +46,7 @@ import {
|
|
|
46
46
|
parseConfig,
|
|
47
47
|
percentChange,
|
|
48
48
|
plural
|
|
49
|
-
} from "./chunk-
|
|
49
|
+
} from "./chunk-FY53B4NB.js";
|
|
50
50
|
|
|
51
51
|
// src/tui/index.tsx
|
|
52
52
|
import { render } from "ink";
|