flet-box 1.0.6 → 1.0.7
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 +28 -1
- package/bin/cli.js +77 -11
- package/bin/commands/auditFix.js +682 -0
- package/bin/commands/checkApi.js +7 -5
- package/bin/commands/create.js +40 -10
- package/bin/commands/createBundle.js +54 -1
- package/bin/commands/images.js +349 -0
- package/bin/commands/pruneAssets.js +170 -0
- package/bin/commands/release.js +34 -1
- package/bin/commands/runCloudflare.js +47 -9
- package/bin/commands/runServer.js +81 -24
- package/bin/commands/toPng.js +346 -0
- package/bin/commands/unpack.js +1173 -0
- package/bin/ota/loader.js +90 -0
- package/bin/utils/appSources.js +101 -0
- package/bin/utils/bundleConfig.js +166 -0
- package/bin/utils/dtsProps.js +147 -0
- package/bin/utils/imageTools.js +271 -0
- package/bin/utils/images/bmp.js +99 -0
- package/bin/utils/images/gif.js +244 -0
- package/bin/utils/images/index.js +163 -0
- package/bin/utils/images/jpeg.js +643 -0
- package/bin/utils/images/png.js +486 -0
- package/bin/utils/images/unsupported.js +29 -0
- package/bin/utils/neutralise.js +109 -6
- package/bin/utils/templates.js +307 -64
- package/docs/cli/README.md +82 -0
- package/docs/guides/online-updates.md +57 -9
- package/docs/widget/Accordion.md +1 -0
- package/docs/widget/AdMob.md +1 -0
- package/docs/widget/AdSense.md +1 -0
- package/docs/widget/AnimatedBox.md +1 -0
- package/docs/widget/AnimatedText.md +1 -0
- package/docs/widget/Audio.md +2 -1
- package/docs/widget/Avatar.md +12 -3
- package/docs/widget/Badge.md +1 -0
- package/docs/widget/Button.md +1 -0
- package/docs/widget/Card.md +1 -0
- package/docs/widget/Carousel.md +1 -0
- package/docs/widget/Chart.md +1 -0
- package/docs/widget/Checkbox.md +1 -0
- package/docs/widget/Chip.md +1 -0
- package/docs/widget/CircularBar.md +1 -0
- package/docs/widget/CircularChart.md +1 -0
- package/docs/widget/CodeViewer.md +1 -0
- package/docs/widget/Column.md +1 -0
- package/docs/widget/Container.md +1 -0
- package/docs/widget/DataTable.md +1 -0
- package/docs/widget/Divider.md +1 -0
- package/docs/widget/DraggBox.md +3 -2
- package/docs/widget/Dropdown.md +3 -0
- package/docs/widget/DroppBox.md +2 -1
- package/docs/widget/FloatingActionButton.md +1 -0
- package/docs/widget/GridView.md +1 -0
- package/docs/widget/Icon.md +1 -0
- package/docs/widget/Image.md +11 -3
- package/docs/widget/Input.md +1 -0
- package/docs/widget/InstallButton.md +1 -0
- package/docs/widget/ListTile.md +1 -0
- package/docs/widget/ListView.md +1 -0
- package/docs/widget/Maps.md +188 -0
- package/docs/widget/Markdown.md +1 -0
- package/docs/widget/MatrixRain.md +1 -0
- package/docs/widget/Pagination.md +1 -0
- package/docs/widget/ParallaxBox.md +1 -0
- package/docs/widget/ProgressBar.md +1 -0
- package/docs/widget/QRCode.md +1 -0
- package/docs/widget/README.md +12 -10
- package/docs/widget/Radio.md +1 -0
- package/docs/widget/Rating.md +1 -0
- package/docs/widget/Row.md +1 -0
- package/docs/widget/Skeleton.md +1 -0
- package/docs/widget/Slider.md +1 -0
- package/docs/widget/Stack.md +1 -0
- package/docs/widget/Stepper.md +1 -0
- package/docs/widget/Switch.md +1 -0
- package/docs/widget/Text.md +1 -0
- package/docs/widget/Tooltip.md +11 -13
- package/docs/widget/TreeView.md +1 -0
- package/docs/widget/Video.md +3 -2
- package/package.json +2 -2
- package/src/core/hmr-client.js +129 -34
- package/src/index.d.ts +132 -0
- package/src/index.js +1 -0
- package/src/navigations/AppBar.js +15 -4
- package/src/navigations/Drawer.js +9 -1
- package/src/widget-factory/translateProps.js +32 -1
- package/src/widgets/Avatar.js +228 -131
- package/src/widgets/Container.js +13 -0
- package/src/widgets/Dropdown.js +33 -1
- package/src/widgets/Image.js +102 -3
- package/src/widgets/Maps.js +1495 -0
- package/src/widgets/Tooltip.js +12 -2
- package/src/widgets/index.js +1 -0
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @file bin/commands/toPng.js
|
|
3
|
+
* @description `flet-box to-png` — converts the images of a folder to PNG.
|
|
4
|
+
*
|
|
5
|
+
* JPEG, BMP, GIF and palette PNG files are decoded with the readers in
|
|
6
|
+
* `bin/utils/images` and re-written as PNG by `encodePng`, which chooses the
|
|
7
|
+
* layout (palette, grey, no alpha when none is used) and the row filters that
|
|
8
|
+
* make the smallest file for those exact pixels, using nothing outside Node core.
|
|
9
|
+
* That is what makes the command safe to ship inside a package that has no
|
|
10
|
+
* runtime dependencies, and it is also its honest limit: WebP, AVIF, TIFF, ICO
|
|
11
|
+
* and SVG are reported as declined, because reading them needs a codec or a
|
|
12
|
+
* rasteriser this repo does not have.
|
|
13
|
+
*
|
|
14
|
+
* Nothing is overwritten by default. A `logo.jpg` becomes `logo.png` beside it;
|
|
15
|
+
* if a `logo.png` already exists the file is skipped and said so, because that
|
|
16
|
+
* collision usually means someone already converted it by hand and the two
|
|
17
|
+
* files are not the same picture. `--replace` deletes the source, and only
|
|
18
|
+
* after the PNG it replaces has been read back and checked pixel for pixel.
|
|
19
|
+
* `--optimize` rewrites a PNG that is already there, and only when the new bytes
|
|
20
|
+
* are smaller and hold the same picture.
|
|
21
|
+
*/
|
|
22
|
+
|
|
23
|
+
import fs from "fs";
|
|
24
|
+
import path from "path";
|
|
25
|
+
import { c, section, dot } from "../utils/colors.js";
|
|
26
|
+
import { decodeImage, encodePng, sniffFormat, UnsupportedImage } from "../utils/images/index.js";
|
|
27
|
+
|
|
28
|
+
const USAGE = `Usage: flet-box to-png [dir] [options]
|
|
29
|
+
|
|
30
|
+
dir Folder to convert (default: ./assets, or ./public/assets)
|
|
31
|
+
|
|
32
|
+
Options:
|
|
33
|
+
--out <dir> Write the PNGs there instead of beside the source
|
|
34
|
+
--optimize Rewrite files that already are PNG, and only when that makes them smaller
|
|
35
|
+
--replace Delete each source file after its PNG was written and read back
|
|
36
|
+
--force Overwrite an existing PNG with the same name
|
|
37
|
+
--recursive Descend into sub-folders (default: yes)
|
|
38
|
+
--flat Only the top level of the folder
|
|
39
|
+
--dry-run Report what would happen; write nothing
|
|
40
|
+
--quiet Only the summary line`;
|
|
41
|
+
|
|
42
|
+
/** Folders tried when no path is given, in order. */
|
|
43
|
+
const DEFAULT_DIRS = ["assets", "public/assets", "src/assets"];
|
|
44
|
+
|
|
45
|
+
/** Never wandered into, however deep it sits. */
|
|
46
|
+
const SKIPPED_DIRS = new Set(["node_modules", ".git", "dist", "build", ".cache"]);
|
|
47
|
+
|
|
48
|
+
/**
|
|
49
|
+
* Image-ish files to look at — including the formats this tool will decline.
|
|
50
|
+
*
|
|
51
|
+
* A `.webp` in an assets folder is exactly the asset the developer is asking
|
|
52
|
+
* about, so it is opened, sniffed and reported rather than passed over because
|
|
53
|
+
* its extension is not on a short list. The extension only picks what to read;
|
|
54
|
+
* the bytes still decide what it is.
|
|
55
|
+
*/
|
|
56
|
+
const IMAGE_FILE = /\.(jpe?g|jfif|png|apng|gif|bmp|webp|avif|heic|heif|tif|tiff|ico|cur|svg)$/i;
|
|
57
|
+
|
|
58
|
+
/**
|
|
59
|
+
* Collects candidate files under `dir`.
|
|
60
|
+
*
|
|
61
|
+
* @param {string} dir
|
|
62
|
+
* @param {boolean} recursive
|
|
63
|
+
* @returns {string[]} Absolute paths, sorted.
|
|
64
|
+
*/
|
|
65
|
+
const collect = (dir, recursive) => {
|
|
66
|
+
const found = [];
|
|
67
|
+
const walk = (current) => {
|
|
68
|
+
for (const entry of fs.readdirSync(current, { withFileTypes: true })) {
|
|
69
|
+
const full = path.join(current, entry.name);
|
|
70
|
+
if (entry.isDirectory()) {
|
|
71
|
+
if (recursive && !SKIPPED_DIRS.has(entry.name)) walk(full);
|
|
72
|
+
continue;
|
|
73
|
+
}
|
|
74
|
+
if (entry.isFile() && IMAGE_FILE.test(entry.name)) found.push(full);
|
|
75
|
+
}
|
|
76
|
+
};
|
|
77
|
+
if (fs.existsSync(dir)) walk(dir);
|
|
78
|
+
return found.sort();
|
|
79
|
+
};
|
|
80
|
+
|
|
81
|
+
/**
|
|
82
|
+
* Byte counts as the report prints them.
|
|
83
|
+
*
|
|
84
|
+
* @param {number} bytes
|
|
85
|
+
* @returns {string}
|
|
86
|
+
*/
|
|
87
|
+
const size = (bytes) =>
|
|
88
|
+
bytes >= 1024 * 1024 ? `${(bytes / 1024 / 1024).toFixed(1)} MB` : `${Math.max(1, Math.round(bytes / 1024))} KB`;
|
|
89
|
+
|
|
90
|
+
/**
|
|
91
|
+
* Decodes PNG bytes in memory and asks whether they are still this picture.
|
|
92
|
+
*
|
|
93
|
+
* The writer now chooses palettes and drops alpha channels, so "same size" is no
|
|
94
|
+
* longer evidence of anything: the test is that every pixel reads back equal.
|
|
95
|
+
* Doing it before the write is what lets `--optimize` refuse a rewrite instead of
|
|
96
|
+
* replacing a good asset with a plausible one.
|
|
97
|
+
*
|
|
98
|
+
* @param {Buffer} png - File bytes about to be written.
|
|
99
|
+
* @param {{ width: number, height: number, rgba: Buffer }} image
|
|
100
|
+
* @returns {boolean}
|
|
101
|
+
*/
|
|
102
|
+
const pngHoldsThePicture = (png, image) => {
|
|
103
|
+
try {
|
|
104
|
+
const decoded = decodeImage(png);
|
|
105
|
+
return (
|
|
106
|
+
decoded.width === image.width &&
|
|
107
|
+
decoded.height === image.height &&
|
|
108
|
+
decoded.rgba.length === image.rgba.length &&
|
|
109
|
+
decoded.rgba.equals(image.rgba)
|
|
110
|
+
);
|
|
111
|
+
} catch {
|
|
112
|
+
return false;
|
|
113
|
+
}
|
|
114
|
+
};
|
|
115
|
+
|
|
116
|
+
/**
|
|
117
|
+
* Reads a file back and confirms it is the PNG that was just written.
|
|
118
|
+
*
|
|
119
|
+
* A truncated write is the failure mode worth guarding: `--replace` would
|
|
120
|
+
* otherwise delete the only copy of the picture.
|
|
121
|
+
*
|
|
122
|
+
* @param {string} file
|
|
123
|
+
* @param {{ width: number, height: number, rgba: Buffer }} expected
|
|
124
|
+
* @returns {boolean}
|
|
125
|
+
*/
|
|
126
|
+
const writtenPngMatches = (file, expected) => {
|
|
127
|
+
try {
|
|
128
|
+
const buf = fs.readFileSync(file);
|
|
129
|
+
if (sniffFormat(buf) !== "png") return false;
|
|
130
|
+
return pngHoldsThePicture(buf, expected);
|
|
131
|
+
} catch {
|
|
132
|
+
return false;
|
|
133
|
+
}
|
|
134
|
+
};
|
|
135
|
+
|
|
136
|
+
/**
|
|
137
|
+
* Runs the conversion.
|
|
138
|
+
*
|
|
139
|
+
* @param {string[]} args - CLI arguments after the command name.
|
|
140
|
+
* @returns {Promise<{ exitCode: number }>} Non-zero only when a file failed to decode.
|
|
141
|
+
*/
|
|
142
|
+
export const toPng = async (args = []) => {
|
|
143
|
+
const has = (flag) => args.includes(flag);
|
|
144
|
+
const valueOf = (flag) => {
|
|
145
|
+
const index = args.indexOf(flag);
|
|
146
|
+
return index >= 0 ? args[index + 1] : undefined;
|
|
147
|
+
};
|
|
148
|
+
|
|
149
|
+
if (has("--help") || has("-h")) {
|
|
150
|
+
console.log(USAGE);
|
|
151
|
+
return { exitCode: 0 };
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
const quiet = has("--quiet");
|
|
155
|
+
const dryRun = has("--dry-run");
|
|
156
|
+
const replace = has("--replace");
|
|
157
|
+
const force = has("--force");
|
|
158
|
+
const optimize = has("--optimize");
|
|
159
|
+
const recursive = !has("--flat");
|
|
160
|
+
const outDir = valueOf("--out");
|
|
161
|
+
|
|
162
|
+
const positional = args.filter((a, i) => !a.startsWith("--") && args[i - 1] !== "--out");
|
|
163
|
+
let target = positional[0] ? path.resolve(positional[0]) : null;
|
|
164
|
+
if (!target) {
|
|
165
|
+
const candidate = DEFAULT_DIRS.map((dir) => path.resolve(dir)).find((dir) => fs.existsSync(dir));
|
|
166
|
+
if (!candidate) {
|
|
167
|
+
console.log(c("red", `Nothing to convert: none of ${DEFAULT_DIRS.join(", ")} exists here.`));
|
|
168
|
+
console.log(c("gray", " Pass a folder: flet-box to-png <dir>"));
|
|
169
|
+
return { exitCode: 1 };
|
|
170
|
+
}
|
|
171
|
+
target = candidate;
|
|
172
|
+
}
|
|
173
|
+
if (!fs.existsSync(target) || !fs.statSync(target).isDirectory()) {
|
|
174
|
+
console.log(c("red", `Not a folder: ${target}`));
|
|
175
|
+
return { exitCode: 1 };
|
|
176
|
+
}
|
|
177
|
+
if (outDir) fs.mkdirSync(path.resolve(outDir), { recursive: true });
|
|
178
|
+
|
|
179
|
+
const files = collect(target, recursive);
|
|
180
|
+
const lines = [];
|
|
181
|
+
let converted = 0;
|
|
182
|
+
let declined = 0;
|
|
183
|
+
let skipped = 0;
|
|
184
|
+
let clash = 0;
|
|
185
|
+
let failed = 0;
|
|
186
|
+
let optimized = 0;
|
|
187
|
+
let optimal = 0;
|
|
188
|
+
let saved = 0;
|
|
189
|
+
let grew = 0;
|
|
190
|
+
|
|
191
|
+
for (const file of files) {
|
|
192
|
+
const relative = path.relative(process.cwd(), file) || path.basename(file);
|
|
193
|
+
let image;
|
|
194
|
+
let bytes;
|
|
195
|
+
try {
|
|
196
|
+
bytes = fs.readFileSync(file);
|
|
197
|
+
image = decodeImage(bytes);
|
|
198
|
+
} catch (error) {
|
|
199
|
+
if (error instanceof UnsupportedImage) {
|
|
200
|
+
declined++;
|
|
201
|
+
lines.push(` ${dot("#f59e0b")} ${relative} — ${c("yellow", "declined")} — ${error.message}`);
|
|
202
|
+
} else {
|
|
203
|
+
failed++;
|
|
204
|
+
lines.push(` ${dot("#ef4444")} ${relative} — ${c("red", "unreadable")} — ${error.message}`);
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// Already PNG bytes: the only question is whether this writer can make them
|
|
210
|
+
// smaller, and `--optimize` is what asks it. A 16-bit source is left alone
|
|
211
|
+
// either way: the reader narrows it to the high byte, so rewriting would
|
|
212
|
+
// trade someone else's bit depth for a smaller file, and "smaller without
|
|
213
|
+
// losing quality" does not cover that.
|
|
214
|
+
if (image.format === "png" && !force) {
|
|
215
|
+
if (bytes.length > 25 && bytes[24] === 16) {
|
|
216
|
+
skipped++;
|
|
217
|
+
lines.push(` ${dot("#64748b")} ${relative} — 16-bit PNG, left as it is (${image.width}×${image.height})`);
|
|
218
|
+
continue;
|
|
219
|
+
}
|
|
220
|
+
if (!optimize) {
|
|
221
|
+
skipped++;
|
|
222
|
+
lines.push(` ${dot("#64748b")} ${relative} — already a PNG (${image.width}×${image.height})`);
|
|
223
|
+
continue;
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
const before = bytes.length;
|
|
227
|
+
const png = encodePng(image);
|
|
228
|
+
if (png.length >= before) {
|
|
229
|
+
optimal++;
|
|
230
|
+
lines.push(` ${dot("#64748b")} ${relative} — already the smallest this writer makes it (${size(before)})`);
|
|
231
|
+
continue;
|
|
232
|
+
}
|
|
233
|
+
if (!pngHoldsThePicture(png, image)) {
|
|
234
|
+
failed++;
|
|
235
|
+
lines.push(` ${dot("#ef4444")} ${relative} — ${c("red", "failed")} — the rewrite does not read back as the same picture`);
|
|
236
|
+
continue;
|
|
237
|
+
}
|
|
238
|
+
saved += before - png.length;
|
|
239
|
+
if (dryRun) {
|
|
240
|
+
optimized++;
|
|
241
|
+
lines.push(` ${dot("#22d3ee")} ${relative} — ${size(before)} → ${size(png.length)}${c("dim", " [dry run]")}`);
|
|
242
|
+
continue;
|
|
243
|
+
}
|
|
244
|
+
// With no --out the rewrite replaces the file, and it does so by writing
|
|
245
|
+
// beside it and renaming: an in-place rewrite that stops halfways would
|
|
246
|
+
// leave the asset smaller *and* unreadable, with the original already gone.
|
|
247
|
+
const destination = outDir ? path.resolve(outDir, path.basename(file)) : file;
|
|
248
|
+
if (destination !== file) {
|
|
249
|
+
fs.writeFileSync(destination, png);
|
|
250
|
+
} else {
|
|
251
|
+
const temp = `${file}.fletbox-replace.tmp`;
|
|
252
|
+
try {
|
|
253
|
+
fs.writeFileSync(temp, png);
|
|
254
|
+
fs.renameSync(temp, file);
|
|
255
|
+
} catch (error) {
|
|
256
|
+
fs.rmSync(temp, { force: true });
|
|
257
|
+
failed++;
|
|
258
|
+
lines.push(` ${dot("#ef4444")} ${relative} — ${c("red", "failed")} — ${error.message}`);
|
|
259
|
+
continue;
|
|
260
|
+
}
|
|
261
|
+
}
|
|
262
|
+
optimized++;
|
|
263
|
+
lines.push(` ${dot("#34d399")} ${relative} — ${c("dim", `optimized ${size(before)} → ${size(png.length)} (−${size(before - png.length)})`)}`);
|
|
264
|
+
continue;
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
const destination = path.resolve(
|
|
268
|
+
outDir ? path.resolve(outDir) : path.dirname(file),
|
|
269
|
+
path.basename(file, path.extname(file)) + ".png",
|
|
270
|
+
);
|
|
271
|
+
|
|
272
|
+
// A file that is merely *named* .png but holds JPEG bytes would otherwise be
|
|
273
|
+
// overwritten in place, which is the one thing this command never does unless
|
|
274
|
+
// it is told to.
|
|
275
|
+
if (destination === file && !force) {
|
|
276
|
+
skipped++;
|
|
277
|
+
lines.push(` ${dot("#64748b")} ${relative} — already named .png (${image.width}×${image.height})`);
|
|
278
|
+
continue;
|
|
279
|
+
}
|
|
280
|
+
// Checked before the dry-run branch, so a rehearsal reports the files it
|
|
281
|
+
// would refuse instead of counting them as conversions waiting to happen.
|
|
282
|
+
if (destination !== file && fs.existsSync(destination) && !force) {
|
|
283
|
+
clash++;
|
|
284
|
+
lines.push(` ${dot("#f59e0b")} ${relative} — ${c("yellow", "kept")} — ${path.basename(destination)} already exists${dryRun ? c("dim", " [dry run]") : " (--force to replace it)"}`);
|
|
285
|
+
continue;
|
|
286
|
+
}
|
|
287
|
+
|
|
288
|
+
const png = encodePng({ width: image.width, height: image.height, rgba: image.rgba });
|
|
289
|
+
const note = image.note ? c("dim", ` (${image.note})`) : "";
|
|
290
|
+
|
|
291
|
+
// PNG keeps every pixel, so the file this writes for a photograph is several
|
|
292
|
+
// times the JPEG it came from. That is the format, not a bug, and the report
|
|
293
|
+
// says it in numbers instead of letting someone discover it in a deploy.
|
|
294
|
+
const sourceSize = bytes.length;
|
|
295
|
+
if (png.length > sourceSize) grew++;
|
|
296
|
+
const sizeNote = c("dim", png.length > sourceSize
|
|
297
|
+
? `${size(png.length)} · ${(png.length / sourceSize).toFixed(1)}× the ${image.format} it came from`
|
|
298
|
+
: size(png.length));
|
|
299
|
+
|
|
300
|
+
if (dryRun) {
|
|
301
|
+
converted++;
|
|
302
|
+
lines.push(` ${dot("#22d3ee")} ${relative} → ${path.relative(process.cwd(), destination) || destination} ${image.width}×${image.height} ${sizeNote}${note} ${c("dim", "[dry run]")}`);
|
|
303
|
+
continue;
|
|
304
|
+
}
|
|
305
|
+
|
|
306
|
+
fs.writeFileSync(destination, png);
|
|
307
|
+
if (!writtenPngMatches(destination, image)) {
|
|
308
|
+
failed++;
|
|
309
|
+
lines.push(` ${dot("#ef4444")} ${relative} — ${c("red", "failed")} — the PNG written back does not match`);
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
let removed = "";
|
|
314
|
+
if (replace && destination !== file) {
|
|
315
|
+
fs.rmSync(file);
|
|
316
|
+
removed = c("dim", " · source removed");
|
|
317
|
+
}
|
|
318
|
+
converted++;
|
|
319
|
+
lines.push(` ${dot("#34d399")} ${relative} → ${path.relative(process.cwd(), destination) || destination} ${image.width}×${image.height} ${sizeNote}${note}${removed}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
if (!quiet) {
|
|
323
|
+
console.log(section("🖼", `to-png — ${path.relative(process.cwd(), target) || "."}`));
|
|
324
|
+
for (const line of lines) console.log(line);
|
|
325
|
+
}
|
|
326
|
+
if (!files.length) console.log(c("gray", " No image files in that folder."));
|
|
327
|
+
|
|
328
|
+
const parts = [`${converted} converted`, `${declined} declined`, `${skipped} already png`];
|
|
329
|
+
if (optimized) parts.push(`${optimized} optimized`);
|
|
330
|
+
if (optimal) parts.push(`${optimal} already optimal`);
|
|
331
|
+
if (saved) parts.push(c("green", `−${size(saved)}`));
|
|
332
|
+
if (clash) parts.push(`${clash} name clash`);
|
|
333
|
+
if (failed) parts.push(`${failed} failed`);
|
|
334
|
+
console.log(`\n ${parts.join(" · ")}${dryRun ? c("yellow", " · nothing written (--dry-run)") : ""}\n`);
|
|
335
|
+
|
|
336
|
+
// The advice belongs with the count, not with ninety individual lines.
|
|
337
|
+
if (grew) {
|
|
338
|
+
console.log(c("yellow", ` ${grew} of the written PNG${grew === 1 ? " is" : "s are"} bigger than the file it came from.`));
|
|
339
|
+
console.log(c("gray", " A PNG stores every pixel, so a photograph is never small in it: keep the JPEG (or a WebP from a tool with a codec) for that picture and let this convert the flat art, logos and icons."));
|
|
340
|
+
console.log("");
|
|
341
|
+
}
|
|
342
|
+
|
|
343
|
+
return { exitCode: failed ? 1 : 0 };
|
|
344
|
+
};
|
|
345
|
+
|
|
346
|
+
export default toPng;
|