@divriots/jampack 0.2.3 → 0.4.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 +34 -59
- package/dist/cache.d.ts +7 -0
- package/dist/cache.js +52 -0
- package/dist/compress.d.ts +4 -2
- package/dist/compress.js +45 -14
- package/dist/config.d.ts +2 -2
- package/dist/index.js +11 -4
- package/dist/optimize.js +209 -111
- package/dist/state.d.ts +6 -4
- package/dist/types.d.ts +2 -2
- package/dist/utils/resource.d.ts +31 -0
- package/dist/utils/resource.js +110 -0
- package/dist/utils.d.ts +0 -2
- package/dist/utils.js +0 -13
- package/dist/version.d.ts +1 -0
- package/dist/version.js +3 -0
- package/package.json +11 -9
- package/dist/compressors/image.d.ts +0 -1
- package/dist/compressors/image.js +0 -1
- package/dist/optimizers/img.d.ts +0 -1
- package/dist/optimizers/img.js +0 -1
- package/dist/options.d.ts +0 -30
- package/dist/options.js +0 -59
- package/dist/state copy.d.ts +0 -25
- package/dist/state copy.js +0 -45
package/README.md
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
<div>
|
|
2
|
+
|
|
1
3
|
```
|
|
2
4
|
__ __
|
|
3
5
|
|__|____ _____ ___________ ____ | | __
|
|
@@ -7,14 +9,17 @@
|
|
|
7
9
|
\______| \/ \/| | \/ \/ \/
|
|
8
10
|
|__|
|
|
9
11
|
```
|
|
12
|
+
|
|
13
|
+
</div>
|
|
14
|
+
|
|
10
15
|
[](https://npmjs.org/package/@divriots/jampack)
|
|
11
16
|
[](https://discord.gg/XkQxSU9)
|
|
12
17
|
|
|
13
|
-
#
|
|
18
|
+
# Jampack
|
|
14
19
|
|
|
15
20
|
Optimizes static websites for best user experience and best Core Web Vitals scores.
|
|
16
21
|
|
|
17
|
-
<div>
|
|
22
|
+
<div id="banner">
|
|
18
23
|
<div>Brought to you by</div>
|
|
19
24
|
<a href="https://divRIOTS.com#gh-light-mode-only">
|
|
20
25
|
<img width="150" height="40" src="https://divRIOTS.com/divriots.svg" alt="‹div›RIOTS" />
|
|
@@ -32,47 +37,11 @@ Optimizes static websites for best user experience and best Core Web Vitals scor
|
|
|
32
37
|
|
|
33
38
|
## Why `jampack`?
|
|
34
39
|
|
|
35
|
-
- **Better user experience
|
|
40
|
+
- **Better user experience** for your website visitors. Specially on low-end devices.
|
|
36
41
|
- **Better SEO**. Page experience now [affects ranking](https://developers.google.com/search/docs/appearance/page-experience).
|
|
37
42
|
- **Easier maintenance**. Generating top performance webpages requires extra engineering in the composition of the pages that is counterproductive with the maintenance of the website.
|
|
38
43
|
- **Time saving**. Spend less time optimizing and let `jampack` perform the heaving-lifting for you.
|
|
39
44
|
|
|
40
|
-
## Optimizations
|
|
41
|
-
|
|
42
|
-
### PASS 1 - Optimizations
|
|
43
|
-
|
|
44
|
-
This pass takes all `*.{html|.htm}` files and parse them for optimisation.
|
|
45
|
-
|
|
46
|
-
| Optimization | Technical details | Related links | Available |
|
|
47
|
-
| -------------- | --------------------- |:------------:|:---------:|
|
|
48
|
-
| 🔽 `<img>` | | | |
|
|
49
|
-
| Optimize `png` to `WebP` | Given `foo.png`, creates a **near loss less** `foo.png.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
50
|
-
| Optimize `jpeg` to `WebP` | Given `foo.jpg`, creates a **lossly** `foo.jpg.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
51
|
-
| Optimize `svg` | | | ✅ |
|
|
52
|
-
| Generate responsive images and set `srcset` | Multiple smaller images are generated exclusively in `WebP` and `srcset` attribute is set accordingly. | [web.dev](https://web.dev/patterns/web-vitals-patterns/images/responsive-images/) | ✅ |
|
|
53
|
-
| Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
|
|
54
|
-
| Set `width` and `height` if missing | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
55
|
-
| Fix `width` or `height` if not numerical | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
56
|
-
| Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
|
|
57
|
-
| Set async decoding by default | Sets `decoding="async"`. | | ✅ |
|
|
58
|
-
| Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
|
|
59
|
-
| 🔽 `<picture>` | | | |
|
|
60
|
-
| - TODO | | | ❌ |
|
|
61
|
-
|
|
62
|
-
### PASS 2 - Compression
|
|
63
|
-
|
|
64
|
-
This pass compresses all untouched files without changing filename or format.
|
|
65
|
-
|
|
66
|
-
| Extension | Compressor | Some options |
|
|
67
|
-
| --------------- | --------------------- | ------------ |
|
|
68
|
-
| `.html`,`.htm` | html-minifier-terser | |
|
|
69
|
-
| `.css` | csso | |
|
|
70
|
-
| `.js` | swc | |
|
|
71
|
-
| `.svg` | svgo | |
|
|
72
|
-
| `.jpg`,`.jpeg` | sharp | |
|
|
73
|
-
| `.png` | sharp | |
|
|
74
|
-
| `.webp` | sharp | |
|
|
75
|
-
|
|
76
45
|
## Quick use
|
|
77
46
|
|
|
78
47
|
```sh
|
|
@@ -80,28 +49,11 @@ This pass compresses all untouched files without changing filename or format.
|
|
|
80
49
|
npx @divriots/jampack ./dist
|
|
81
50
|
```
|
|
82
51
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
| Option | Description |
|
|
86
|
-
| ----------- | ------------------------------ |
|
|
87
|
-
| `--onlyoptim` | Only runs optimization (PASS 1) |
|
|
88
|
-
| `--onlycomp` | Only runs compression (PASS 2) |
|
|
89
|
-
| `--fast` | Go fast. Mostly no compression just checks for issues. |
|
|
90
|
-
| `--fail` | Exits with a non-zero return code if issues. |
|
|
91
|
-
| `--nowrite` | Don't write anything to disk (for testing) |
|
|
92
|
-
| `--exclude` | Files to exclude from processing. Expect glob format like `--exclude 'blog/**'` |
|
|
93
|
-
|
|
94
|
-
## Configuration
|
|
95
|
-
|
|
96
|
-
No configuration yet. All defaults.
|
|
52
|
+
For more options see [CLI options](https://jampack.divriots.com/cli-options).
|
|
97
53
|
|
|
98
|
-
##
|
|
54
|
+
## Documentation
|
|
99
55
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
### Add `loading="eager"` to your images above the fold
|
|
103
|
-
|
|
104
|
-
`jampack` sets all images to lazy by default. But, [images above the fold should not be lazy](https://web.dev/lazy-loading-images/#effects-on-largest-contentful-paint-lcp). Just add `loading="eager"` to your images above the fold and `jampack` will treat them as high priority.
|
|
56
|
+
Check the [Documentation side](https://jampack.divriots.com).
|
|
105
57
|
|
|
106
58
|
## `jampack` use in the wild
|
|
107
59
|
|
|
@@ -119,3 +71,26 @@ No configuration yet. All defaults.
|
|
|
119
71
|
## License
|
|
120
72
|
|
|
121
73
|
This software is released under the terms of the [MIT license](https://github.com/divriots/jampack/blob/main/LICENSE).
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
## Optimizations
|
|
77
|
+
|
|
78
|
+
### PASS 1 - Optimizations
|
|
79
|
+
|
|
80
|
+
This pass takes all `*.{html|.htm}` files and parse them for optimisation.
|
|
81
|
+
|
|
82
|
+
| Optimization | Technical details | Related links | Available |
|
|
83
|
+
| -------------- | --------------------- |:------------:|:---------:|
|
|
84
|
+
| 🔽 `<img>` | | | |
|
|
85
|
+
| Optimize `png` to `WebP` | Given `foo.png`, creates a **near loss less** `foo.png.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
86
|
+
| Optimize `jpeg` to `WebP` | Given `foo.jpg`, creates a **lossly** `foo.jpg.webp` if size reduced. | [web.dev](https://web.dev/uses-webp-images/) | ✅ |
|
|
87
|
+
| Optimize `svg` | | | ✅ |
|
|
88
|
+
| Generate responsive images and set `srcset` | Multiple smaller images are generated exclusively in `WebP` and `srcset` attribute is set accordingly. | [web.dev](https://web.dev/patterns/web-vitals-patterns/images/responsive-images/) | ✅ |
|
|
89
|
+
| Embed small images | If optimised image size is <500 bytes, image is embed in `[src]` | | ✅ |
|
|
90
|
+
| Set `width` and `height` if missing | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
91
|
+
| Fix `width` or `height` if not numerical | Uses image size and aspect ratio to fill values | [web.dev](https://web.dev/optimize-cls/#images-without-dimensions) | ✅ |
|
|
92
|
+
| Set lazy loading by default | Sets `loading="lazy"` if `[loading]` is not set. | [web.dev](https://web.dev/lazy-loading-images/) | ✅ |
|
|
93
|
+
| Set async decoding by default | Sets `decoding="async"`. | | ✅ |
|
|
94
|
+
| Warn on missing `[alt]` | Warn on missing `alt` attribute but adds `alt=""` in case image is considered decorative. | | ✅ |
|
|
95
|
+
| 🔽 `<picture>` | | | |
|
|
96
|
+
| - TODO | | | ❌ |
|
package/dist/cache.d.ts
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Image } from "./types.js";
|
|
3
|
+
declare function cleanCache(): Promise<void>;
|
|
4
|
+
declare function computeCacheHash(buffer: Buffer, options?: any): string;
|
|
5
|
+
declare function getFromCacheCompressImage(hash: string): Promise<Image | undefined>;
|
|
6
|
+
declare function addToCacheCompressImage(hash: string, image: Image): Promise<void>;
|
|
7
|
+
export { cleanCache, computeCacheHash, getFromCacheCompressImage, addToCacheCompressImage };
|
package/dist/cache.js
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
import hasha from "hasha";
|
|
2
|
+
import path from "path";
|
|
3
|
+
import * as fs from "fs/promises";
|
|
4
|
+
import { VERSION } from './version.js';
|
|
5
|
+
import $state from './state.js';
|
|
6
|
+
const CACHE_FOLDER = ".jampack/cache";
|
|
7
|
+
async function cleanCache() {
|
|
8
|
+
try {
|
|
9
|
+
await fs.rm(CACHE_FOLDER, { recursive: true });
|
|
10
|
+
}
|
|
11
|
+
catch (e) {
|
|
12
|
+
// Nothing to do, probably not present
|
|
13
|
+
}
|
|
14
|
+
}
|
|
15
|
+
function computeCacheHash(buffer, options) {
|
|
16
|
+
if ($state.args.nocache) {
|
|
17
|
+
return '';
|
|
18
|
+
}
|
|
19
|
+
let hash = `${VERSION}/${hasha(buffer, { algorithm: 'sha256' })}`;
|
|
20
|
+
if (options) {
|
|
21
|
+
hash += "/" + hasha(JSON.stringify(options), { algorithm: 'md5' });
|
|
22
|
+
}
|
|
23
|
+
return hash;
|
|
24
|
+
}
|
|
25
|
+
function getLocation(hash) {
|
|
26
|
+
return path.join(CACHE_FOLDER, hash);
|
|
27
|
+
}
|
|
28
|
+
async function getFromCacheCompressImage(hash) {
|
|
29
|
+
if ($state.args.nocache) {
|
|
30
|
+
return undefined;
|
|
31
|
+
}
|
|
32
|
+
const location = getLocation(hash);
|
|
33
|
+
try {
|
|
34
|
+
const data = await fs.readFile(path.join(location, 'data'));
|
|
35
|
+
const format = JSON.parse((await fs.readFile(path.join(location, 'format'))).toString());
|
|
36
|
+
return { data, format };
|
|
37
|
+
}
|
|
38
|
+
catch (e) {
|
|
39
|
+
// Problem during cache loading or not in cache
|
|
40
|
+
}
|
|
41
|
+
return undefined;
|
|
42
|
+
}
|
|
43
|
+
async function addToCacheCompressImage(hash, image) {
|
|
44
|
+
if ($state.args.nocache) {
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
const location = getLocation(hash);
|
|
48
|
+
await fs.mkdir(location, { recursive: true });
|
|
49
|
+
await fs.writeFile(path.join(location, 'data'), image.data);
|
|
50
|
+
await fs.writeFile(path.join(location, 'format'), JSON.stringify(image.format));
|
|
51
|
+
}
|
|
52
|
+
export { cleanCache, computeCacheHash, getFromCacheCompressImage, addToCacheCompressImage };
|
package/dist/compress.d.ts
CHANGED
|
@@ -1,5 +1,7 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
1
|
import sharp from 'sharp';
|
|
3
2
|
import type { Image } from './types.js';
|
|
4
|
-
export declare const compressImage: (data: Buffer,
|
|
3
|
+
export declare const compressImage: (data: Buffer, options: {
|
|
4
|
+
resize?: sharp.ResizeOptions;
|
|
5
|
+
toFormat?: 'webp' | 'pjpg' | 'unchanged';
|
|
6
|
+
}) => Promise<Image | undefined>;
|
|
5
7
|
export declare function compress(exclude: string): Promise<void>;
|
package/dist/compress.js
CHANGED
|
@@ -10,6 +10,17 @@ import $state from './state.js';
|
|
|
10
10
|
import { globby } from 'globby';
|
|
11
11
|
import config from './config.js';
|
|
12
12
|
import ora from 'ora';
|
|
13
|
+
import { getFromCacheCompressImage, addToCacheCompressImage, computeCacheHash } from './cache.js';
|
|
14
|
+
async function compressJS(code) {
|
|
15
|
+
const newjsresult = await swc.minify(code, { compress: true, mangle: true });
|
|
16
|
+
return newjsresult.code;
|
|
17
|
+
}
|
|
18
|
+
async function compressInlineJS(code) {
|
|
19
|
+
const newCode = await compressJS(code);
|
|
20
|
+
if (newCode && newCode.length < code.length)
|
|
21
|
+
return newCode;
|
|
22
|
+
return code;
|
|
23
|
+
}
|
|
13
24
|
const processFile = async (file, stats) => {
|
|
14
25
|
let writeData = undefined;
|
|
15
26
|
try {
|
|
@@ -28,7 +39,7 @@ const processFile = async (file, stats) => {
|
|
|
28
39
|
case '.html':
|
|
29
40
|
case '.htm':
|
|
30
41
|
const htmldata = await fs.readFile(file);
|
|
31
|
-
const newhtmlData = await htmlminifier(htmldata.toString(), { minifyCSS: true, minifyJS:
|
|
42
|
+
const newhtmlData = await htmlminifier(htmldata.toString(), { minifyCSS: true, minifyJS: compressInlineJS, sortClassName: true, sortAttributes: true });
|
|
32
43
|
writeData = newhtmlData;
|
|
33
44
|
break;
|
|
34
45
|
case '.css':
|
|
@@ -40,9 +51,9 @@ const processFile = async (file, stats) => {
|
|
|
40
51
|
break;
|
|
41
52
|
case '.js':
|
|
42
53
|
const jsdata = await fs.readFile(file);
|
|
43
|
-
const
|
|
44
|
-
if (
|
|
45
|
-
writeData =
|
|
54
|
+
const newJS = await compressJS(jsdata.toString());
|
|
55
|
+
if (newJS && newJS.length < jsdata.length) {
|
|
56
|
+
writeData = newJS;
|
|
46
57
|
}
|
|
47
58
|
break;
|
|
48
59
|
}
|
|
@@ -74,7 +85,15 @@ function createWebpOptions(opt) {
|
|
|
74
85
|
effort: opt.effort
|
|
75
86
|
};
|
|
76
87
|
}
|
|
77
|
-
export const compressImage = async (data,
|
|
88
|
+
export const compressImage = async (data, options) => {
|
|
89
|
+
var _a, _b;
|
|
90
|
+
const cacheHash = computeCacheHash(data, options);
|
|
91
|
+
const imageFromCache = await getFromCacheCompressImage(cacheHash);
|
|
92
|
+
if (imageFromCache) {
|
|
93
|
+
return imageFromCache;
|
|
94
|
+
}
|
|
95
|
+
// Load modifiable toFormat
|
|
96
|
+
let toFormat = options.toFormat || 'unchanged';
|
|
78
97
|
if (!config.image.compress)
|
|
79
98
|
return undefined;
|
|
80
99
|
let sharpFile = await sharp(data, { animated: true });
|
|
@@ -106,7 +125,7 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
106
125
|
return { format: 'svg', data: Buffer.from(newData.data, 'utf8') };
|
|
107
126
|
case 'png':
|
|
108
127
|
outputFormat = 'png';
|
|
109
|
-
if (
|
|
128
|
+
if (toFormat === 'unchanged') {
|
|
110
129
|
sharpFile = sharpFile.png(config.image.png.options || {});
|
|
111
130
|
}
|
|
112
131
|
doSharp = true;
|
|
@@ -114,7 +133,7 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
114
133
|
case 'jpeg':
|
|
115
134
|
case 'jpg':
|
|
116
135
|
outputFormat = 'jpg';
|
|
117
|
-
if (
|
|
136
|
+
if (toFormat === 'unchanged') {
|
|
118
137
|
sharpFile = sharpFile.jpeg(config.image.jpeg.options || {});
|
|
119
138
|
}
|
|
120
139
|
doSharp = true;
|
|
@@ -123,24 +142,36 @@ export const compressImage = async (data, resize, toWebp = false) => {
|
|
|
123
142
|
outputFormat = 'webp';
|
|
124
143
|
sharpFile = sharpFile.webp(createWebpOptions(config.image.webp.options_lossly) || {});
|
|
125
144
|
doSharp = true;
|
|
126
|
-
|
|
145
|
+
toFormat = 'unchanged'; // Don't need to convert to webP again
|
|
127
146
|
break;
|
|
128
147
|
}
|
|
129
148
|
if (doSharp) {
|
|
130
|
-
if (
|
|
149
|
+
if (toFormat === 'webp') {
|
|
131
150
|
sharpFile = sharpFile.webp(createWebpOptions(meta.format === 'png' ? config.image.webp.options_lossless : config.image.webp.options_lossly));
|
|
132
151
|
outputFormat = 'webp';
|
|
133
152
|
}
|
|
134
|
-
if (
|
|
135
|
-
sharpFile = sharpFile.
|
|
153
|
+
else if (toFormat === 'pjpg') {
|
|
154
|
+
sharpFile = sharpFile.jpeg({ ...config.image.jpeg.options, progressive: true } || {});
|
|
155
|
+
outputFormat = 'jpg';
|
|
156
|
+
}
|
|
157
|
+
if (((_a = options.resize) === null || _a === void 0 ? void 0 : _a.width) && ((_b = options.resize) === null || _b === void 0 ? void 0 : _b.height)) {
|
|
158
|
+
sharpFile = sharpFile.resize({ ...options.resize, fit: 'fill', withoutEnlargement: true });
|
|
136
159
|
}
|
|
137
|
-
|
|
160
|
+
//
|
|
161
|
+
// Output image processed
|
|
162
|
+
//
|
|
163
|
+
// Add to cache
|
|
164
|
+
const outputImage = { format: outputFormat, data: await sharpFile.toBuffer() };
|
|
165
|
+
await addToCacheCompressImage(cacheHash, outputImage);
|
|
166
|
+
// Go
|
|
167
|
+
return outputImage;
|
|
138
168
|
}
|
|
169
|
+
// Unknown input format or error while processing
|
|
139
170
|
return undefined;
|
|
140
171
|
};
|
|
141
|
-
const compressImageFile = async (file
|
|
172
|
+
const compressImageFile = async (file) => {
|
|
142
173
|
const buffer = await fs.readFile(file);
|
|
143
|
-
return compressImage(buffer, {}
|
|
174
|
+
return compressImage(buffer, {});
|
|
144
175
|
};
|
|
145
176
|
export async function compress(exclude) {
|
|
146
177
|
const spinner = ora(getProgressText()).start();
|
package/dist/config.d.ts
CHANGED
package/dist/index.js
CHANGED
|
@@ -9,13 +9,14 @@ import { fast } from './config.js';
|
|
|
9
9
|
import { printTitle } from './logger.js';
|
|
10
10
|
import { exit } from 'process';
|
|
11
11
|
import kleur from 'kleur';
|
|
12
|
+
import { cleanCache } from './cache.js';
|
|
12
13
|
const logo = ` __ __
|
|
13
14
|
|__|____ _____ ___________ ____ | | __
|
|
14
15
|
| \\__ \\ / \\\\____ \\__ \\ _/ ___\\| |/ /
|
|
15
16
|
| |/ __ \\| Y Y \\ |_> > __ \\\\ \\___| <
|
|
16
17
|
/\\__| (____ /__|_| / __(____ /\\___ >__|_ \\
|
|
17
18
|
\\______| \\/ \\/| | \\/ \\/ \\/
|
|
18
|
-
|__| by ‹div›RIOTS (c)2022
|
|
19
|
+
|__| by ‹div›RIOTS (c)2022-2023
|
|
19
20
|
`;
|
|
20
21
|
console.log(logo);
|
|
21
22
|
const program = new Command();
|
|
@@ -32,6 +33,8 @@ program.command('pack', { isDefault: true })
|
|
|
32
33
|
.option('--fail', 'Exits with a non-zero return code if issues.')
|
|
33
34
|
.option('--onlyoptim', 'Only optimize (PASS 1).')
|
|
34
35
|
.option('--onlycomp', 'Only compress (PASS 2).')
|
|
36
|
+
.option('--cleancache', 'Clean cache before running')
|
|
37
|
+
.option('--nocache', 'Run with no use of cache')
|
|
35
38
|
.action(async (dir, options) => {
|
|
36
39
|
$state.dir = dir;
|
|
37
40
|
$state.args = options;
|
|
@@ -39,6 +42,10 @@ program.command('pack', { isDefault: true })
|
|
|
39
42
|
if (options.fast) {
|
|
40
43
|
fast();
|
|
41
44
|
}
|
|
45
|
+
// Clean cache
|
|
46
|
+
if (options.cleancache) {
|
|
47
|
+
cleanCache();
|
|
48
|
+
}
|
|
42
49
|
if (!options.onlycomp) {
|
|
43
50
|
printTitle('PASS 1 - Optimizing');
|
|
44
51
|
console.time('Done');
|
|
@@ -94,13 +101,13 @@ function printIssues() {
|
|
|
94
101
|
printTitle('Issues', kleur.bgRed);
|
|
95
102
|
console.log('');
|
|
96
103
|
for (let [file, list] of $state.issues) {
|
|
97
|
-
console.log(
|
|
104
|
+
console.log('▶ ' + file + '\n');
|
|
98
105
|
list.forEach(issue => {
|
|
99
106
|
issueCount++;
|
|
100
|
-
console.log(
|
|
107
|
+
console.log(`${kleur.bgYellow(` ${issue.type} `)} ${issue.msg}\n`);
|
|
101
108
|
});
|
|
102
109
|
}
|
|
110
|
+
printTitle(`${issueCount} issue(s) over ${$state.issues.size} files`, kleur.bgYellow);
|
|
103
111
|
console.log('');
|
|
104
|
-
printTitle(`${issueCount} issue(s) over ${$state.issues.size} files`, kleur.bgRed);
|
|
105
112
|
}
|
|
106
113
|
}
|