@d-zero/archaeologist 1.0.0-alpha.2
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 +34 -0
- package/dist/analyze.d.ts +2 -0
- package/dist/analyze.js +132 -0
- package/dist/archaeologist.d.ts +2 -0
- package/dist/archaeologist.js +15 -0
- package/dist/cli.d.ts +2 -0
- package/dist/cli.js +29 -0
- package/dist/diff-images.d.ts +28 -0
- package/dist/diff-images.js +41 -0
- package/dist/diff-tree-color.d.ts +1 -0
- package/dist/diff-tree-color.js +15 -0
- package/dist/diff-tree.d.ts +7 -0
- package/dist/diff-tree.js +32 -0
- package/dist/get-data.d.ts +4 -0
- package/dist/get-data.js +24 -0
- package/dist/index.d.ts +1 -0
- package/dist/index.js +1 -0
- package/dist/output-utils.d.ts +3 -0
- package/dist/output-utils.js +9 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.js +1 -0
- package/dist/utils.d.ts +2 -0
- package/dist/utils.js +9 -0
- package/package.json +49 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2024 D-ZERO Co., Ltd.
|
|
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,34 @@
|
|
|
1
|
+
# `@d-zero/archaeologist`
|
|
2
|
+
|
|
3
|
+
**🕵️ Archaeologist: アーキオロジスト**
|
|
4
|
+
|
|
5
|
+
ウェブサイトの本番環境と開発環境や、新旧のページの比較するためのツールです。
|
|
6
|
+
|
|
7
|
+
- Puppeteerを実行してページのスクリーンショットを撮影します
|
|
8
|
+
- スクリーンショットはデスクトップとモバイルの2つのサイズでそれぞれ撮影します
|
|
9
|
+
- スクリーンショットは画像差分(ビジュアルリグレッション)を検出・出力します
|
|
10
|
+
- HTMLの差分も検出します
|
|
11
|
+
|
|
12
|
+
## CLI
|
|
13
|
+
|
|
14
|
+
```sh
|
|
15
|
+
npx @d-zero/archaeologist -f <filepath>
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
URLリストを持つファイルを指定して実行します。
|
|
19
|
+
|
|
20
|
+
ファイルの先頭には比較対象のホストを指定します。[Frontmatter](https://jekyllrb.com/docs/front-matter/)形式で`comparisonHost`に指定します。
|
|
21
|
+
|
|
22
|
+
```txt
|
|
23
|
+
---
|
|
24
|
+
comparisonHost: https://stage.example.com
|
|
25
|
+
---
|
|
26
|
+
|
|
27
|
+
https://example.com
|
|
28
|
+
https://example.com/a
|
|
29
|
+
https://example.com/b
|
|
30
|
+
https://example.com/c
|
|
31
|
+
https://example.com/xyz/001
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
実行した結果は`.archaeologist`ディレクトリに保存されます。
|
package/dist/analyze.js
ADDED
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
import { writeFile, mkdir } from 'node:fs/promises';
|
|
2
|
+
import path from 'node:path';
|
|
3
|
+
import { deal } from '@d-zero/dealer';
|
|
4
|
+
import c from 'ansi-colors';
|
|
5
|
+
import puppeteer from 'puppeteer';
|
|
6
|
+
import { diffImages } from './diff-images.js';
|
|
7
|
+
import { diffTree } from './diff-tree.js';
|
|
8
|
+
import { getData } from './get-data.js';
|
|
9
|
+
import { label, score } from './output-utils.js';
|
|
10
|
+
export async function analyze(list) {
|
|
11
|
+
const browser = await puppeteer.launch({
|
|
12
|
+
args: [
|
|
13
|
+
//
|
|
14
|
+
'--lang=ja',
|
|
15
|
+
'--no-zygote',
|
|
16
|
+
'--ignore-certificate-errors',
|
|
17
|
+
],
|
|
18
|
+
});
|
|
19
|
+
const results = [];
|
|
20
|
+
const dir = path.resolve(process.cwd(), '.archaeologist');
|
|
21
|
+
await mkdir(dir, { recursive: true }).catch(() => { });
|
|
22
|
+
await deal(list,
|
|
23
|
+
//
|
|
24
|
+
async (urlPair, update, index) => {
|
|
25
|
+
const page = await browser.newPage();
|
|
26
|
+
page.setDefaultNavigationTimeout(0);
|
|
27
|
+
await page.setUserAgent('Mozilla/5.0 (Macintosh; Intel Mac OS X 10_15_7) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/123.0.0.0 Safari/537.36');
|
|
28
|
+
await page.setExtraHTTPHeaders({
|
|
29
|
+
'Accept-Language': 'ja-JP',
|
|
30
|
+
});
|
|
31
|
+
return async () => {
|
|
32
|
+
const dataPair = [];
|
|
33
|
+
for (const url of urlPair) {
|
|
34
|
+
const data = await getData(page, url, (phase, data) => {
|
|
35
|
+
const outputUrl = c.gray(url);
|
|
36
|
+
const sizeName = label(data.name);
|
|
37
|
+
switch (phase) {
|
|
38
|
+
case 'setViewport': {
|
|
39
|
+
const { width } = data;
|
|
40
|
+
update(`%braille% ${outputUrl} ${sizeName}: ↔️ Change viewport size to ${width}px`);
|
|
41
|
+
break;
|
|
42
|
+
}
|
|
43
|
+
case 'load': {
|
|
44
|
+
const { type } = data;
|
|
45
|
+
update(`%braille% ${outputUrl} ${sizeName}: %earth% ${type === 'open' ? 'Open' : 'Reload'} page`);
|
|
46
|
+
break;
|
|
47
|
+
}
|
|
48
|
+
case 'scroll': {
|
|
49
|
+
update(`%braille% ${outputUrl} ${sizeName}: %propeller% Scroll the page`);
|
|
50
|
+
break;
|
|
51
|
+
}
|
|
52
|
+
case 'screenshotStart': {
|
|
53
|
+
update(`%braille% ${outputUrl} ${sizeName}: 📸 Take a screenshot`);
|
|
54
|
+
break;
|
|
55
|
+
}
|
|
56
|
+
case 'screenshotEnd': {
|
|
57
|
+
const { binary } = data;
|
|
58
|
+
update(`%braille% ${outputUrl} ${sizeName}: 📸 Screenshot taken (${binary.length} bytes)`);
|
|
59
|
+
break;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
});
|
|
63
|
+
dataPair.push(data);
|
|
64
|
+
await delay(600);
|
|
65
|
+
}
|
|
66
|
+
const [a, b] = dataPair;
|
|
67
|
+
if (!a || !b) {
|
|
68
|
+
throw new Error('Failed to get screenshots');
|
|
69
|
+
}
|
|
70
|
+
const screenshotResult = {};
|
|
71
|
+
const outputUrl = c.gray(urlPair.join(' vs '));
|
|
72
|
+
for (const [name, screenshotA] of Object.entries(a.screenshots)) {
|
|
73
|
+
const screenshotB = b.screenshots[name];
|
|
74
|
+
const sizeName = label(name);
|
|
75
|
+
const id = `${index}_${name}`;
|
|
76
|
+
if (!screenshotB) {
|
|
77
|
+
throw new Error(`Failed to get screenshotB: ${id}`);
|
|
78
|
+
}
|
|
79
|
+
const imageDiff = await diffImages(screenshotA, screenshotB, (phase, data) => {
|
|
80
|
+
switch (phase) {
|
|
81
|
+
case 'create': {
|
|
82
|
+
update(`%braille% ${outputUrl} ${sizeName}: 🖼️ Create images`);
|
|
83
|
+
break;
|
|
84
|
+
}
|
|
85
|
+
case 'resize': {
|
|
86
|
+
const { width, height } = data;
|
|
87
|
+
update(`%braille% ${outputUrl} ${sizeName}: ↔️ Resize images to ${width}x${height}`);
|
|
88
|
+
break;
|
|
89
|
+
}
|
|
90
|
+
case 'diff': {
|
|
91
|
+
update(`%braille% ${outputUrl} ${sizeName}: 📊 Compare images`);
|
|
92
|
+
break;
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
});
|
|
96
|
+
update(`%braille% ${outputUrl} ${sizeName}: 🧩 Matches ${score(imageDiff.matches, 0.9)}`);
|
|
97
|
+
await delay(1500);
|
|
98
|
+
await writeFile(path.resolve(dir, `${id}_a.png`), imageDiff.images.a);
|
|
99
|
+
await writeFile(path.resolve(dir, `${id}_b.png`), imageDiff.images.b);
|
|
100
|
+
const outFilePath = path.resolve(dir, `${id}_diff.png`);
|
|
101
|
+
update(`%braille% ${outputUrl} ${sizeName}: 📊 Save diff image to ${path.relative(dir, outFilePath)}`);
|
|
102
|
+
await writeFile(outFilePath, imageDiff.images.diff);
|
|
103
|
+
screenshotResult[name] = {
|
|
104
|
+
matches: imageDiff.matches,
|
|
105
|
+
file: outFilePath,
|
|
106
|
+
};
|
|
107
|
+
}
|
|
108
|
+
const htmlDiff = diffTree(a.serializedHtml, b.serializedHtml);
|
|
109
|
+
const outFilePath = path.resolve(dir, `${index}_html.diff`);
|
|
110
|
+
await writeFile(outFilePath, htmlDiff.result, { encoding: 'utf8' });
|
|
111
|
+
const result = {
|
|
112
|
+
target: [a.url, b.url],
|
|
113
|
+
screenshots: screenshotResult,
|
|
114
|
+
html: {
|
|
115
|
+
matches: htmlDiff.matches,
|
|
116
|
+
diff: htmlDiff.changed ? htmlDiff.result : null,
|
|
117
|
+
file: outFilePath,
|
|
118
|
+
},
|
|
119
|
+
};
|
|
120
|
+
results.push(result);
|
|
121
|
+
};
|
|
122
|
+
}, {
|
|
123
|
+
header(_, done, total) {
|
|
124
|
+
return `${c.bold.magenta('🕵️ Archaeologist')} ${done}/${total}`;
|
|
125
|
+
},
|
|
126
|
+
});
|
|
127
|
+
await browser.close();
|
|
128
|
+
return results;
|
|
129
|
+
}
|
|
130
|
+
function delay(ms) {
|
|
131
|
+
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
132
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
import { analyze } from './analyze.js';
|
|
3
|
+
import { label, score } from './output-utils.js';
|
|
4
|
+
export async function archaeologist(list) {
|
|
5
|
+
const results = await analyze(list);
|
|
6
|
+
const output = [];
|
|
7
|
+
for (const result of results) {
|
|
8
|
+
output.push(c.gray(`${result.target.join(' vs ')}`));
|
|
9
|
+
for (const [sizeName, { matches, file }] of Object.entries(result.screenshots)) {
|
|
10
|
+
output.push(` ${label(sizeName)} ${score(matches, 0.9)} ${file}`);
|
|
11
|
+
}
|
|
12
|
+
output.push(` ${label('HTML', c.bgBlueBright)}: ${score(result.html.matches, 0.995)} ${result.html.file}`);
|
|
13
|
+
}
|
|
14
|
+
process.stdout.write(output.join('\n') + '\n');
|
|
15
|
+
}
|
package/dist/cli.d.ts
ADDED
package/dist/cli.js
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import fs from 'node:fs/promises';
|
|
3
|
+
import { toList } from '@d-zero/readtext/list';
|
|
4
|
+
import fm from 'front-matter';
|
|
5
|
+
import minimist from 'minimist';
|
|
6
|
+
import { archaeologist } from './archaeologist.js';
|
|
7
|
+
const cli = minimist(process.argv.slice(2), {
|
|
8
|
+
alias: {
|
|
9
|
+
f: 'listfile',
|
|
10
|
+
},
|
|
11
|
+
});
|
|
12
|
+
if (cli.listfile) {
|
|
13
|
+
const fileContent = await fs.readFile(cli.listfile, 'utf8');
|
|
14
|
+
const content =
|
|
15
|
+
// @ts-ignore
|
|
16
|
+
fm(fileContent);
|
|
17
|
+
const urlList = toList(content.body);
|
|
18
|
+
const pairList = urlList.map((urlStr) => {
|
|
19
|
+
const url = new URL(urlStr);
|
|
20
|
+
return [
|
|
21
|
+
url.toString(),
|
|
22
|
+
`${content.attributes.comparisonHost}${url.pathname}${url.search}`,
|
|
23
|
+
];
|
|
24
|
+
});
|
|
25
|
+
await archaeologist(pairList);
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
process.stdout.write('Usage: archaeologist -f <listfile>\n');
|
|
29
|
+
process.exit(1);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
/// <reference types="node" resolution-mode="require"/>
|
|
2
|
+
import type { Screenshot } from '@d-zero/puppeteer-screenshot';
|
|
3
|
+
export type DiffImagesPhase = {
|
|
4
|
+
create: {
|
|
5
|
+
a: Buffer;
|
|
6
|
+
b: Buffer;
|
|
7
|
+
};
|
|
8
|
+
resize: {
|
|
9
|
+
a: Buffer;
|
|
10
|
+
b: Buffer;
|
|
11
|
+
width: number;
|
|
12
|
+
height: number;
|
|
13
|
+
};
|
|
14
|
+
diff: {
|
|
15
|
+
a: Buffer;
|
|
16
|
+
b: Buffer;
|
|
17
|
+
};
|
|
18
|
+
};
|
|
19
|
+
type DiffImagesListener = (phase: keyof DiffImagesPhase, data: DiffImagesPhase[keyof DiffImagesPhase]) => void;
|
|
20
|
+
export declare function diffImages(dataA: Screenshot, dataB: Screenshot, listener: DiffImagesListener): Promise<{
|
|
21
|
+
matches: number;
|
|
22
|
+
images: {
|
|
23
|
+
a: Buffer;
|
|
24
|
+
b: Buffer;
|
|
25
|
+
diff: Buffer;
|
|
26
|
+
};
|
|
27
|
+
}>;
|
|
28
|
+
export {};
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
import Jimp from 'jimp';
|
|
2
|
+
import pixelmatch from 'pixelmatch';
|
|
3
|
+
import { PNG } from 'pngjs';
|
|
4
|
+
export async function diffImages(dataA, dataB, listener) {
|
|
5
|
+
listener('create', { a: dataA.binary, b: dataB.binary });
|
|
6
|
+
const imgA = PNG.sync.read(dataA.binary);
|
|
7
|
+
const imgB = PNG.sync.read(dataB.binary);
|
|
8
|
+
const width = Math.max(imgA.width, imgB.width);
|
|
9
|
+
const height = Math.max(imgA.height, imgB.height);
|
|
10
|
+
listener('resize', { a: dataA.binary, b: dataB.binary, width, height });
|
|
11
|
+
const resizedA = await resizeImg(dataA.binary, width, height);
|
|
12
|
+
const resizedB = await resizeImg(dataB.binary, width, height);
|
|
13
|
+
listener('diff', { a: resizedA, b: resizedB });
|
|
14
|
+
const imgA_ = PNG.sync.read(resizedA);
|
|
15
|
+
const imgB_ = PNG.sync.read(resizedB);
|
|
16
|
+
const diffImage = new PNG({ width, height });
|
|
17
|
+
const matcheBytes = pixelmatch(imgA_.data, imgB_.data, diffImage.data, width, height);
|
|
18
|
+
const matches = 1 - matcheBytes / (width * height);
|
|
19
|
+
const imageABuffer = PNG.sync.write(imgA_);
|
|
20
|
+
const imageBBuffer = PNG.sync.write(imgB_);
|
|
21
|
+
const imageDiffBuffer = PNG.sync.write(diffImage);
|
|
22
|
+
return {
|
|
23
|
+
matches,
|
|
24
|
+
images: {
|
|
25
|
+
a: imageABuffer,
|
|
26
|
+
b: imageBBuffer,
|
|
27
|
+
diff: imageDiffBuffer,
|
|
28
|
+
},
|
|
29
|
+
};
|
|
30
|
+
}
|
|
31
|
+
async function resizeImg(bin, width, height) {
|
|
32
|
+
const img = await Jimp.read(bin);
|
|
33
|
+
img.contain(width, height, Jimp.HORIZONTAL_ALIGN_LEFT | Jimp.VERTICAL_ALIGN_TOP);
|
|
34
|
+
return new Promise((resolve, reject) => {
|
|
35
|
+
img.getBuffer(Jimp.MIME_PNG, (err, buffer) => {
|
|
36
|
+
if (err)
|
|
37
|
+
reject(err);
|
|
38
|
+
resolve(buffer);
|
|
39
|
+
});
|
|
40
|
+
});
|
|
41
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function diffTreeColor(diff: string): string;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
export function diffTreeColor(diff) {
|
|
3
|
+
return diff
|
|
4
|
+
.split('\n')
|
|
5
|
+
.map((line) => {
|
|
6
|
+
if (line.startsWith('+')) {
|
|
7
|
+
return c.green(line);
|
|
8
|
+
}
|
|
9
|
+
if (line.startsWith('-')) {
|
|
10
|
+
return c.red(line);
|
|
11
|
+
}
|
|
12
|
+
return c.gray(line);
|
|
13
|
+
})
|
|
14
|
+
.join('\n');
|
|
15
|
+
}
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import { diffLines } from 'diff';
|
|
2
|
+
export function diffTree(dataA, dataB) {
|
|
3
|
+
const changes = diffLines(dataA, dataB);
|
|
4
|
+
const lineA = dataA.split('\n').length;
|
|
5
|
+
const lineB = dataB.split('\n').length;
|
|
6
|
+
const maxLine = Math.max(lineA, lineB);
|
|
7
|
+
let changedLines = 0;
|
|
8
|
+
const result = changes
|
|
9
|
+
.map((change) => {
|
|
10
|
+
if (change.added) {
|
|
11
|
+
changedLines++;
|
|
12
|
+
return `+${change.value}`;
|
|
13
|
+
}
|
|
14
|
+
if (change.removed) {
|
|
15
|
+
changedLines++;
|
|
16
|
+
return `-${change.value}`;
|
|
17
|
+
}
|
|
18
|
+
return change.value
|
|
19
|
+
.split('\n')
|
|
20
|
+
.map((line) => (line.trim() ? ` ${line}` : line))
|
|
21
|
+
.join('\n');
|
|
22
|
+
})
|
|
23
|
+
.filter(Boolean)
|
|
24
|
+
.join('');
|
|
25
|
+
return {
|
|
26
|
+
changed: changedLines > 0,
|
|
27
|
+
maxLine,
|
|
28
|
+
changedLines,
|
|
29
|
+
matches: 1 - changedLines / maxLine,
|
|
30
|
+
result,
|
|
31
|
+
};
|
|
32
|
+
}
|
package/dist/get-data.js
ADDED
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
import { distill } from '@d-zero/html-distiller';
|
|
2
|
+
import { screenshot } from '@d-zero/puppeteer-screenshot';
|
|
3
|
+
export async function getData(page, url, listener) {
|
|
4
|
+
const screenshots = await screenshot(page, url, {
|
|
5
|
+
sizes: {
|
|
6
|
+
desktop: {
|
|
7
|
+
width: 1280,
|
|
8
|
+
},
|
|
9
|
+
mobile: {
|
|
10
|
+
width: 375,
|
|
11
|
+
resolution: 2,
|
|
12
|
+
},
|
|
13
|
+
},
|
|
14
|
+
listener,
|
|
15
|
+
});
|
|
16
|
+
const html = await page.content();
|
|
17
|
+
const serializedHtmlTree = distill(html).tree;
|
|
18
|
+
const serializedHtml = JSON.stringify(serializedHtmlTree, null, 2);
|
|
19
|
+
return {
|
|
20
|
+
url,
|
|
21
|
+
serializedHtml,
|
|
22
|
+
screenshots,
|
|
23
|
+
};
|
|
24
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { archaeologist } from './archaeologist.js';
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export { archaeologist } from './archaeologist.js';
|
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
export function label(str, color = c.bgMagenta) {
|
|
3
|
+
return color(` ${str} `);
|
|
4
|
+
}
|
|
5
|
+
export function score(matches, threshold) {
|
|
6
|
+
const color = matches > threshold ? c.green : c.red;
|
|
7
|
+
const num = (matches * 100).toFixed(1);
|
|
8
|
+
return c.bold(color(`${num}%`));
|
|
9
|
+
}
|
package/dist/types.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
import type { Screenshot } from '@d-zero/puppeteer-screenshot';
|
|
2
|
+
export type PageData = {
|
|
3
|
+
url: string;
|
|
4
|
+
serializedHtml: string;
|
|
5
|
+
screenshots: Record<string, Screenshot>;
|
|
6
|
+
};
|
|
7
|
+
export type URLPair = readonly [urlA: string, urlB: string];
|
|
8
|
+
export type Result = {
|
|
9
|
+
target: [urlA: string, urlB: string];
|
|
10
|
+
screenshots: Record<string, {
|
|
11
|
+
matches: number;
|
|
12
|
+
file: string;
|
|
13
|
+
}>;
|
|
14
|
+
html: {
|
|
15
|
+
matches: number;
|
|
16
|
+
diff: string | null;
|
|
17
|
+
file: string;
|
|
18
|
+
};
|
|
19
|
+
};
|
package/dist/types.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
package/dist/utils.d.ts
ADDED
package/dist/utils.js
ADDED
|
@@ -0,0 +1,9 @@
|
|
|
1
|
+
import c from 'ansi-colors';
|
|
2
|
+
export function label(str) {
|
|
3
|
+
return c.bgMagenta(` ${str} `);
|
|
4
|
+
}
|
|
5
|
+
export function score(matches, threshold) {
|
|
6
|
+
const color = matches > threshold ? c.green : c.red;
|
|
7
|
+
const num = (matches * 100).toFixed(1);
|
|
8
|
+
return c.bold(color(`${num}%`));
|
|
9
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@d-zero/archaeologist",
|
|
3
|
+
"version": "1.0.0-alpha.2",
|
|
4
|
+
"description": "Uncover visual and HTML differences in web pages with precision",
|
|
5
|
+
"author": "D-ZERO",
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"private": false,
|
|
8
|
+
"publishConfig": {
|
|
9
|
+
"access": "public"
|
|
10
|
+
},
|
|
11
|
+
"type": "module",
|
|
12
|
+
"exports": {
|
|
13
|
+
".": {
|
|
14
|
+
"import": "./dist/index.js",
|
|
15
|
+
"types": "./dist/index.d.ts"
|
|
16
|
+
}
|
|
17
|
+
},
|
|
18
|
+
"bin": {
|
|
19
|
+
"archaeologist": "./dist/cli.js"
|
|
20
|
+
},
|
|
21
|
+
"files": [
|
|
22
|
+
"dist"
|
|
23
|
+
],
|
|
24
|
+
"scripts": {
|
|
25
|
+
"build": "tsc",
|
|
26
|
+
"clean": "tsc --build --clean"
|
|
27
|
+
},
|
|
28
|
+
"dependencies": {
|
|
29
|
+
"@d-zero/dealer": "^1.0.0-alpha.2",
|
|
30
|
+
"@d-zero/html-distiller": "^1.0.0-alpha.2",
|
|
31
|
+
"@d-zero/puppeteer-screenshot": "^1.0.0-alpha.2",
|
|
32
|
+
"@d-zero/readtext": "^1.0.0-alpha.2",
|
|
33
|
+
"ansi-colors": "4.1.3",
|
|
34
|
+
"diff": "5.2.0",
|
|
35
|
+
"front-matter": "4.0.2",
|
|
36
|
+
"jimp": "0.22.12",
|
|
37
|
+
"minimist": "1.2.8",
|
|
38
|
+
"pixelmatch": "5.3.0",
|
|
39
|
+
"pngjs": "7.0.0",
|
|
40
|
+
"puppeteer": "22.6.4"
|
|
41
|
+
},
|
|
42
|
+
"devDependencies": {
|
|
43
|
+
"@types/diff": "5.0.9",
|
|
44
|
+
"@types/pixelmatch": "5.2.6",
|
|
45
|
+
"@types/pngjs": "6.0.4",
|
|
46
|
+
"puppeteer": "22.6.4"
|
|
47
|
+
},
|
|
48
|
+
"gitHead": "802df4fdb85892c8fab3bf1b376755e25ca2ca47"
|
|
49
|
+
}
|