@d-zero/archaeologist 1.0.2 → 1.1.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 +9 -1
- package/dist/analyze.d.ts +7 -1
- package/dist/analyze.js +29 -19
- package/dist/archaeologist.d.ts +4 -2
- package/dist/archaeologist.js +8 -5
- package/dist/cli.js +8 -3
- package/dist/diff-images.d.ts +1 -2
- package/dist/diff-images.js +3 -0
- package/dist/diff-tree.d.ts +1 -2
- package/dist/diff-tree.js +10 -24
- package/dist/get-data.d.ts +5 -1
- package/dist/get-data.js +12 -10
- package/dist/read-config.d.ts +1 -1
- package/dist/read-config.js +2 -2
- package/dist/types.d.ts +17 -11
- package/package.json +8 -7
package/README.md
CHANGED
|
@@ -12,11 +12,19 @@
|
|
|
12
12
|
## CLI
|
|
13
13
|
|
|
14
14
|
```sh
|
|
15
|
-
npx @d-zero/archaeologist -f <filepath>
|
|
15
|
+
npx @d-zero/archaeologist -f <filepath> [--limit <number>] [--debug]
|
|
16
16
|
```
|
|
17
17
|
|
|
18
18
|
URLリストを持つファイルを指定して実行します。
|
|
19
19
|
|
|
20
|
+
### オプション
|
|
21
|
+
|
|
22
|
+
- `-f, --file <filepath>`: URLリストを持つファイルのパス(必須)
|
|
23
|
+
- `--limit <number>`: 並列実行数の上限(デフォルト: 10)
|
|
24
|
+
- `--debug`: デバッグモード(デフォルト: false)
|
|
25
|
+
|
|
26
|
+
### ファイルフォーマット
|
|
27
|
+
|
|
20
28
|
ファイルの先頭には比較対象のホストを指定します。[Frontmatter](https://jekyllrb.com/docs/front-matter/)形式で`comparisonHost`に指定します。
|
|
21
29
|
|
|
22
30
|
```txt
|
package/dist/analyze.d.ts
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
1
|
import type { Result, URLPair } from './types.js';
|
|
2
2
|
import type { PageHook } from '@d-zero/puppeteer-screenshot';
|
|
3
|
-
export
|
|
3
|
+
export interface AnalyzeOptions {
|
|
4
|
+
readonly hooks: readonly PageHook[];
|
|
5
|
+
readonly htmlDiffOnly?: boolean;
|
|
6
|
+
readonly limit?: number;
|
|
7
|
+
readonly debug?: boolean;
|
|
8
|
+
}
|
|
9
|
+
export declare function analyze(list: readonly URLPair[], options?: AnalyzeOptions): Promise<Result[]>;
|
package/dist/analyze.js
CHANGED
|
@@ -8,7 +8,7 @@ import { diffImages } from './diff-images.js';
|
|
|
8
8
|
import { diffTree } from './diff-tree.js';
|
|
9
9
|
import { getData } from './get-data.js';
|
|
10
10
|
import { label, score } from './output-utils.js';
|
|
11
|
-
export async function analyze(list,
|
|
11
|
+
export async function analyze(list, options) {
|
|
12
12
|
const urlInfo = analyzeUrlList(list);
|
|
13
13
|
const useOldMode = urlInfo.hasAuth && urlInfo.hasNoSSL;
|
|
14
14
|
const browser = await puppeteer.launch({
|
|
@@ -35,7 +35,9 @@ export async function analyze(list, hooks) {
|
|
|
35
35
|
return async () => {
|
|
36
36
|
const dataPair = [];
|
|
37
37
|
for (const url of urlPair) {
|
|
38
|
-
const data = await getData(page, url,
|
|
38
|
+
const data = await getData(page, url, {
|
|
39
|
+
...options,
|
|
40
|
+
}, (phase, data) => {
|
|
39
41
|
const outputUrl = c.gray(url);
|
|
40
42
|
const sizeName = label(data.name);
|
|
41
43
|
switch (phase) {
|
|
@@ -102,33 +104,41 @@ export async function analyze(list, hooks) {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
});
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
107
|
+
let image = null;
|
|
108
|
+
if (imageDiff) {
|
|
109
|
+
update(`%braille% ${outputUrl} ${sizeName}: 🧩 Matches ${score(imageDiff.matches, 0.9)}`);
|
|
110
|
+
await delay(1500);
|
|
111
|
+
await writeFile(path.resolve(dir, `${id}_a.png`), imageDiff.images.a);
|
|
112
|
+
await writeFile(path.resolve(dir, `${id}_b.png`), imageDiff.images.b);
|
|
113
|
+
const outFilePath = path.resolve(dir, `${id}_diff.png`);
|
|
114
|
+
update(`%braille% ${outputUrl} ${sizeName}: 📊 Save diff image to ${path.relative(dir, outFilePath)}`);
|
|
115
|
+
await writeFile(outFilePath, imageDiff.images.diff);
|
|
116
|
+
image = {
|
|
117
|
+
matches: imageDiff.matches,
|
|
118
|
+
file: outFilePath,
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
const htmlDiff = diffTree(a.url, b.url, screenshotA.domTree, screenshotB.domTree);
|
|
122
|
+
const outFilePath = path.resolve(dir, `${id}_html.diff`);
|
|
123
|
+
await writeFile(outFilePath, htmlDiff.result, { encoding: 'utf8' });
|
|
112
124
|
screenshotResult[name] = {
|
|
113
|
-
|
|
114
|
-
|
|
125
|
+
image,
|
|
126
|
+
dom: {
|
|
127
|
+
matches: htmlDiff.matches,
|
|
128
|
+
diff: htmlDiff.changed ? htmlDiff.result : null,
|
|
129
|
+
file: outFilePath,
|
|
130
|
+
},
|
|
115
131
|
};
|
|
116
132
|
}
|
|
117
|
-
const htmlDiff = diffTree(a.serializedHtml, b.serializedHtml);
|
|
118
|
-
const outFilePath = path.resolve(dir, `${index}_html.diff`);
|
|
119
|
-
await writeFile(outFilePath, htmlDiff.result, { encoding: 'utf8' });
|
|
120
133
|
const result = {
|
|
121
134
|
target: [a.url, b.url],
|
|
122
135
|
screenshots: screenshotResult,
|
|
123
|
-
html: {
|
|
124
|
-
matches: htmlDiff.matches,
|
|
125
|
-
diff: htmlDiff.changed ? htmlDiff.result : null,
|
|
126
|
-
file: outFilePath,
|
|
127
|
-
},
|
|
128
136
|
};
|
|
129
137
|
results.push(result);
|
|
130
138
|
};
|
|
131
139
|
}, {
|
|
140
|
+
limit: options?.limit,
|
|
141
|
+
debug: options?.debug,
|
|
132
142
|
header(_, done, total) {
|
|
133
143
|
return `${c.bold.magenta('🕵️ Archaeologist')} ${done}/${total}`;
|
|
134
144
|
},
|
package/dist/archaeologist.d.ts
CHANGED
|
@@ -1,3 +1,5 @@
|
|
|
1
|
+
import type { AnalyzeOptions } from './analyze.js';
|
|
1
2
|
import type { URLPair } from './types.js';
|
|
2
|
-
|
|
3
|
-
|
|
3
|
+
export interface ArchaeologistOptions extends AnalyzeOptions {
|
|
4
|
+
}
|
|
5
|
+
export declare function archaeologist(list: readonly URLPair[], options?: ArchaeologistOptions): Promise<void>;
|
package/dist/archaeologist.js
CHANGED
|
@@ -1,15 +1,18 @@
|
|
|
1
1
|
import c from 'ansi-colors';
|
|
2
2
|
import { analyze } from './analyze.js';
|
|
3
3
|
import { label, score } from './output-utils.js';
|
|
4
|
-
export async function archaeologist(list,
|
|
5
|
-
const results = await analyze(list,
|
|
4
|
+
export async function archaeologist(list, options) {
|
|
5
|
+
const results = await analyze(list, options);
|
|
6
6
|
const output = [];
|
|
7
7
|
for (const result of results) {
|
|
8
8
|
output.push(c.gray(`${result.target.join(' vs ')}`));
|
|
9
|
-
for (const [sizeName, {
|
|
10
|
-
|
|
9
|
+
for (const [sizeName, { image, dom }] of Object.entries(result.screenshots)) {
|
|
10
|
+
if (image) {
|
|
11
|
+
const { matches, file } = image;
|
|
12
|
+
output.push(` ${label(sizeName)} ${score(matches, 0.9)} ${file}`);
|
|
13
|
+
}
|
|
14
|
+
output.push(` ${label('HTML', c.bgBlueBright)}: ${score(dom.matches, 0.995)} ${dom.file}`);
|
|
11
15
|
}
|
|
12
|
-
output.push(` ${label('HTML', c.bgBlueBright)}: ${score(result.html.matches, 0.995)} ${result.html.file}`);
|
|
13
16
|
}
|
|
14
17
|
process.stdout.write(output.join('\n') + '\n');
|
|
15
18
|
}
|
package/dist/cli.js
CHANGED
|
@@ -8,9 +8,14 @@ const cli = minimist(process.argv.slice(2), {
|
|
|
8
8
|
},
|
|
9
9
|
});
|
|
10
10
|
if (cli.listfile) {
|
|
11
|
-
const { pairList,
|
|
12
|
-
await archaeologist(pairList,
|
|
11
|
+
const { pairList, hooks } = await readConfig(cli.listfile);
|
|
12
|
+
await archaeologist(pairList, {
|
|
13
|
+
hooks,
|
|
14
|
+
limit: cli.limit ? Number.parseInt(cli.limit) : undefined,
|
|
15
|
+
debug: !!cli.debug,
|
|
16
|
+
htmlDiffOnly: !!cli.htmlDiffOnly,
|
|
17
|
+
});
|
|
13
18
|
process.exit(0);
|
|
14
19
|
}
|
|
15
|
-
process.stdout.write('Usage: archaeologist -f <listfile
|
|
20
|
+
process.stdout.write('Usage: archaeologist -f <listfile> [--limit <number>]');
|
|
16
21
|
process.exit(1);
|
package/dist/diff-images.d.ts
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
/// <reference types="node" resolution-mode="require"/>
|
|
2
1
|
import type { Screenshot } from '@d-zero/puppeteer-screenshot';
|
|
3
2
|
export type DiffImagesPhase = {
|
|
4
3
|
create: {
|
|
@@ -24,5 +23,5 @@ export declare function diffImages(dataA: Screenshot, dataB: Screenshot, listene
|
|
|
24
23
|
b: Buffer;
|
|
25
24
|
diff: Buffer;
|
|
26
25
|
};
|
|
27
|
-
}>;
|
|
26
|
+
} | null>;
|
|
28
27
|
export {};
|
package/dist/diff-images.js
CHANGED
|
@@ -2,6 +2,9 @@ import Jimp from 'jimp';
|
|
|
2
2
|
import pixelmatch from 'pixelmatch';
|
|
3
3
|
import { PNG } from 'pngjs';
|
|
4
4
|
export async function diffImages(dataA, dataB, listener) {
|
|
5
|
+
if (!dataA.binary || !dataB.binary) {
|
|
6
|
+
return null;
|
|
7
|
+
}
|
|
5
8
|
listener('create', { a: dataA.binary, b: dataB.binary });
|
|
6
9
|
const imgA = PNG.sync.read(dataA.binary);
|
|
7
10
|
const imgB = PNG.sync.read(dataB.binary);
|
package/dist/diff-tree.d.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
|
-
export declare function diffTree(dataA: string, dataB: string): {
|
|
1
|
+
export declare function diffTree(urlA: string, urlB: string, dataA: string, dataB: string): {
|
|
2
2
|
changed: boolean;
|
|
3
3
|
maxLine: number;
|
|
4
|
-
changedLines: number;
|
|
5
4
|
matches: number;
|
|
6
5
|
result: string;
|
|
7
6
|
};
|
package/dist/diff-tree.js
CHANGED
|
@@ -1,32 +1,18 @@
|
|
|
1
|
-
import {
|
|
2
|
-
|
|
3
|
-
|
|
1
|
+
import { createTwoFilesPatch } from 'diff';
|
|
2
|
+
import parse from 'parse-diff';
|
|
3
|
+
export function diffTree(urlA, urlB, dataA, dataB) {
|
|
4
|
+
const result = createTwoFilesPatch(urlA, urlB, dataA, dataB);
|
|
5
|
+
const info = parse(result)[0];
|
|
6
|
+
if (!info) {
|
|
7
|
+
throw new Error('Failed to parse diff');
|
|
8
|
+
}
|
|
4
9
|
const lineA = dataA.split('\n').length;
|
|
5
10
|
const lineB = dataB.split('\n').length;
|
|
6
11
|
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
12
|
return {
|
|
26
|
-
changed:
|
|
13
|
+
changed: dataA !== dataB,
|
|
27
14
|
maxLine,
|
|
28
|
-
|
|
29
|
-
matches: 1 - changedLines / maxLine,
|
|
15
|
+
matches: 1 - Math.abs((info.additions - info.deletions) / maxLine),
|
|
30
16
|
result,
|
|
31
17
|
};
|
|
32
18
|
}
|
package/dist/get-data.d.ts
CHANGED
|
@@ -1,4 +1,8 @@
|
|
|
1
1
|
import type { PageData } from './types.js';
|
|
2
2
|
import type { Listener, PageHook } from '@d-zero/puppeteer-screenshot';
|
|
3
3
|
import type { Page } from 'puppeteer';
|
|
4
|
-
export
|
|
4
|
+
export interface GetDataOptions {
|
|
5
|
+
readonly hooks?: readonly PageHook[];
|
|
6
|
+
readonly htmlDiffOnly?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export declare function getData(page: Page, url: string, options: GetDataOptions, listener: Listener): Promise<PageData>;
|
package/dist/get-data.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { distill } from '@d-zero/html-distiller';
|
|
2
2
|
import { screenshot } from '@d-zero/puppeteer-screenshot';
|
|
3
|
-
export async function getData(page, url,
|
|
3
|
+
export async function getData(page, url, options, listener) {
|
|
4
|
+
const htmlDiffOnly = options.htmlDiffOnly ?? false;
|
|
4
5
|
const screenshots = await screenshot(page, url, {
|
|
5
6
|
sizes: {
|
|
6
7
|
desktop: {
|
|
@@ -11,15 +12,16 @@ export async function getData(page, url, hooks, listener) {
|
|
|
11
12
|
resolution: 2,
|
|
12
13
|
},
|
|
13
14
|
},
|
|
14
|
-
hooks,
|
|
15
|
+
hooks: options?.hooks ?? [],
|
|
15
16
|
listener,
|
|
17
|
+
domOnly: htmlDiffOnly,
|
|
16
18
|
});
|
|
17
|
-
const
|
|
18
|
-
const
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
19
|
+
const data = { url, screenshots: {} };
|
|
20
|
+
for (const [sizeName, screenshot] of Object.entries(screenshots)) {
|
|
21
|
+
data.screenshots[sizeName] = {
|
|
22
|
+
...screenshot,
|
|
23
|
+
domTree: JSON.stringify(distill(screenshot.dom).tree, null, 2),
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
return data;
|
|
25
27
|
}
|
package/dist/read-config.d.ts
CHANGED
package/dist/read-config.js
CHANGED
|
@@ -15,9 +15,9 @@ export async function readConfig(filePath) {
|
|
|
15
15
|
`${content.attributes.comparisonHost}${url.pathname}${url.search}`,
|
|
16
16
|
];
|
|
17
17
|
});
|
|
18
|
-
const
|
|
18
|
+
const hooks = await readHooks(content.attributes?.hooks ?? [], filePath);
|
|
19
19
|
return {
|
|
20
20
|
pairList,
|
|
21
|
-
|
|
21
|
+
hooks,
|
|
22
22
|
};
|
|
23
23
|
}
|
package/dist/types.d.ts
CHANGED
|
@@ -2,19 +2,25 @@ export type { PageHook } from '@d-zero/puppeteer-screenshot';
|
|
|
2
2
|
import type { Screenshot } from '@d-zero/puppeteer-screenshot';
|
|
3
3
|
export type PageData = {
|
|
4
4
|
url: string;
|
|
5
|
-
|
|
6
|
-
|
|
5
|
+
screenshots: Record<string, Screenshot & {
|
|
6
|
+
domTree: string;
|
|
7
|
+
}>;
|
|
7
8
|
};
|
|
8
9
|
export type URLPair = readonly [urlA: string, urlB: string];
|
|
9
10
|
export type Result = {
|
|
10
11
|
target: [urlA: string, urlB: string];
|
|
11
|
-
screenshots: Record<string,
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
12
|
+
screenshots: Record<string, MediaResult>;
|
|
13
|
+
};
|
|
14
|
+
export type MediaResult = {
|
|
15
|
+
image: ImageResult | null;
|
|
16
|
+
dom: DOMResult;
|
|
17
|
+
};
|
|
18
|
+
export type ImageResult = {
|
|
19
|
+
matches: number;
|
|
20
|
+
file: string;
|
|
21
|
+
};
|
|
22
|
+
export type DOMResult = {
|
|
23
|
+
matches: number;
|
|
24
|
+
diff: string | null;
|
|
25
|
+
file: string;
|
|
20
26
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@d-zero/archaeologist",
|
|
3
|
-
"version": "1.0
|
|
3
|
+
"version": "1.1.0",
|
|
4
4
|
"description": "Uncover visual and HTML differences in web pages with precision",
|
|
5
5
|
"author": "D-ZERO",
|
|
6
6
|
"license": "MIT",
|
|
@@ -26,24 +26,25 @@
|
|
|
26
26
|
"clean": "tsc --build --clean"
|
|
27
27
|
},
|
|
28
28
|
"dependencies": {
|
|
29
|
-
"@d-zero/dealer": "1.0.
|
|
29
|
+
"@d-zero/dealer": "1.0.2",
|
|
30
30
|
"@d-zero/html-distiller": "1.0.0",
|
|
31
|
-
"@d-zero/puppeteer-screenshot": "1.0
|
|
32
|
-
"@d-zero/readtext": "1.0.
|
|
31
|
+
"@d-zero/puppeteer-screenshot": "1.1.0",
|
|
32
|
+
"@d-zero/readtext": "1.0.3",
|
|
33
33
|
"ansi-colors": "4.1.3",
|
|
34
34
|
"diff": "5.2.0",
|
|
35
35
|
"front-matter": "4.0.2",
|
|
36
36
|
"jimp": "0.22.12",
|
|
37
37
|
"minimist": "1.2.8",
|
|
38
|
+
"parse-diff": "0.11.1",
|
|
38
39
|
"pixelmatch": "5.3.0",
|
|
39
40
|
"pngjs": "7.0.0",
|
|
40
|
-
"puppeteer": "22.
|
|
41
|
+
"puppeteer": "22.12.0"
|
|
41
42
|
},
|
|
42
43
|
"devDependencies": {
|
|
43
44
|
"@types/diff": "5.2.1",
|
|
44
45
|
"@types/pixelmatch": "5.2.6",
|
|
45
46
|
"@types/pngjs": "6.0.5",
|
|
46
|
-
"puppeteer": "22.
|
|
47
|
+
"puppeteer": "22.12.0"
|
|
47
48
|
},
|
|
48
|
-
"gitHead": "
|
|
49
|
+
"gitHead": "9b4c167933f33237bb688e4835f17b499dc4c48d"
|
|
49
50
|
}
|