@d-zero/archaeologist 1.1.1 → 1.1.3

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.
@@ -3,5 +3,9 @@ type AnalyzedUrlList = {
3
3
  hasAuth: boolean;
4
4
  hasNoSSL: boolean;
5
5
  };
6
+ /**
7
+ *
8
+ * @param list
9
+ */
6
10
  export declare function analyzeUrlList(list: readonly URLPair[]): AnalyzedUrlList;
7
11
  export {};
@@ -1,3 +1,7 @@
1
+ /**
2
+ *
3
+ * @param list
4
+ */
1
5
  export function analyzeUrlList(list) {
2
6
  const result = {
3
7
  hasAuth: false,
package/dist/analyze.d.ts CHANGED
@@ -6,4 +6,9 @@ export interface AnalyzeOptions {
6
6
  readonly limit?: number;
7
7
  readonly debug?: boolean;
8
8
  }
9
+ /**
10
+ *
11
+ * @param list
12
+ * @param options
13
+ */
9
14
  export declare function analyze(list: readonly URLPair[], options?: AnalyzeOptions): Promise<Result[]>;
package/dist/analyze.js CHANGED
@@ -1,43 +1,36 @@
1
1
  import { writeFile, mkdir } from 'node:fs/promises';
2
2
  import path from 'node:path';
3
- import { deal } from '@d-zero/dealer';
3
+ import { deal } from '@d-zero/puppeteer-dealer';
4
4
  import c from 'ansi-colors';
5
- import puppeteer from 'puppeteer';
6
5
  import { analyzeUrlList } from './analize-url.js';
7
6
  import { diffImages } from './diff-images.js';
8
7
  import { diffTree } from './diff-tree.js';
9
8
  import { getData } from './get-data.js';
10
- import { label, score } from './output-utils.js';
9
+ import { score } from './output-utils.js';
10
+ /**
11
+ *
12
+ * @param list
13
+ * @param options
14
+ */
11
15
  export async function analyze(list, options) {
12
- const urlInfo = analyzeUrlList(list);
13
- const useOldMode = urlInfo.hasAuth && urlInfo.hasNoSSL;
14
- const browser = await puppeteer.launch({
15
- headless: useOldMode ? 'shell' : true,
16
- args: [
17
- //
18
- '--lang=ja',
19
- '--no-zygote',
20
- '--ignore-certificate-errors',
21
- ],
22
- });
23
16
  const results = [];
24
17
  const dir = path.resolve(process.cwd(), '.archaeologist');
25
18
  await mkdir(dir, { recursive: true }).catch(() => { });
26
- await deal(list,
27
- //
28
- async (urlPair, update, index) => {
29
- const page = await browser.newPage();
30
- page.setDefaultNavigationTimeout(0);
31
- 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');
32
- await page.setExtraHTTPHeaders({
33
- 'Accept-Language': 'ja-JP',
34
- });
35
- return async () => {
19
+ const urlInfo = analyzeUrlList(list);
20
+ const useOldMode = urlInfo.hasAuth || urlInfo.hasNoSSL;
21
+ await deal(list.map(([urlA]) => ({ id: null, url: urlA })), (_, done, total) => {
22
+ return `${c.bold.magenta('🕵️ Archaeologist')} ${done}/${total}`;
23
+ }, {
24
+ async deal(page, _, urlA, logger, index) {
25
+ const urlPair = list.find(([url]) => url === urlA);
26
+ if (!urlPair) {
27
+ throw new Error(`Failed to find urlPair: ${urlA}`);
28
+ }
36
29
  const dataPair = [];
37
30
  for (const url of urlPair) {
38
31
  const data = await getData(page, url, {
39
32
  ...options,
40
- }, update);
33
+ }, logger);
41
34
  dataPair.push(data);
42
35
  await delay(600);
43
36
  }
@@ -46,10 +39,10 @@ export async function analyze(list, options) {
46
39
  throw new Error('Failed to get screenshots');
47
40
  }
48
41
  const screenshotResult = {};
49
- const outputUrl = c.gray(urlPair.join(' vs '));
42
+ const outputUrl = 'vs ' + c.gray(urlPair[1]);
50
43
  for (const [name, screenshotA] of Object.entries(a.screenshots)) {
51
44
  const screenshotB = b.screenshots[name];
52
- const sizeName = label(name);
45
+ const sizeName = c.bgMagenta(` ${name} `);
53
46
  const id = `${index}_${name}`;
54
47
  if (!screenshotB) {
55
48
  throw new Error(`Failed to get screenshotB: ${id}`);
@@ -57,28 +50,28 @@ export async function analyze(list, options) {
57
50
  const imageDiff = await diffImages(screenshotA, screenshotB, (phase, data) => {
58
51
  switch (phase) {
59
52
  case 'create': {
60
- update(`%braille% ${outputUrl} ${sizeName}: 🖼️ Create images`);
53
+ logger(`${sizeName} ${outputUrl} 🖼️ Create images`);
61
54
  break;
62
55
  }
63
56
  case 'resize': {
64
57
  const { width, height } = data;
65
- update(`%braille% ${outputUrl} ${sizeName}: ↔️ Resize images to ${width}x${height}`);
58
+ logger(`${sizeName} ${outputUrl} ↔️ Resize images to ${width}x${height}`);
66
59
  break;
67
60
  }
68
61
  case 'diff': {
69
- update(`%braille% ${outputUrl} ${sizeName}: 📊 Compare images`);
62
+ logger(`${sizeName} ${outputUrl} 📊 Compare images`);
70
63
  break;
71
64
  }
72
65
  }
73
66
  });
74
67
  let image = null;
75
68
  if (imageDiff) {
76
- update(`%braille% ${outputUrl} ${sizeName}: 🧩 Matches ${score(imageDiff.matches, 0.9)}`);
69
+ logger(`${sizeName} ${outputUrl} 🧩 Matches ${score(imageDiff.matches, 0.9)}`);
77
70
  await delay(1500);
78
71
  await writeFile(path.resolve(dir, `${id}_a.png`), imageDiff.images.a);
79
72
  await writeFile(path.resolve(dir, `${id}_b.png`), imageDiff.images.b);
80
73
  const outFilePath = path.resolve(dir, `${id}_diff.png`);
81
- update(`%braille% ${outputUrl} ${sizeName}: 📊 Save diff image to ${path.relative(dir, outFilePath)}`);
74
+ logger(`${sizeName} ${outputUrl} 📊 Save diff image to ${path.relative(dir, outFilePath)}`);
82
75
  await writeFile(outFilePath, imageDiff.images.diff);
83
76
  image = {
84
77
  matches: imageDiff.matches,
@@ -102,17 +95,17 @@ export async function analyze(list, options) {
102
95
  screenshots: screenshotResult,
103
96
  };
104
97
  results.push(result);
105
- };
106
- }, {
107
- limit: options?.limit,
108
- debug: options?.debug,
109
- header(_, done, total) {
110
- return `${c.bold.magenta('🕵️ Archaeologist')} ${done}/${total}`;
111
98
  },
99
+ }, {
100
+ ...options,
101
+ headless: useOldMode ? 'shell' : true,
112
102
  });
113
- await browser.close();
114
103
  return results;
115
104
  }
105
+ /**
106
+ *
107
+ * @param ms
108
+ */
116
109
  function delay(ms) {
117
110
  return new Promise((resolve) => setTimeout(resolve, ms));
118
111
  }
@@ -2,4 +2,9 @@ import type { AnalyzeOptions } from './analyze.js';
2
2
  import type { URLPair } from './types.js';
3
3
  export interface ArchaeologistOptions extends AnalyzeOptions {
4
4
  }
5
+ /**
6
+ *
7
+ * @param list
8
+ * @param options
9
+ */
5
10
  export declare function archaeologist(list: readonly URLPair[], options?: ArchaeologistOptions): Promise<void>;
@@ -1,6 +1,11 @@
1
1
  import c from 'ansi-colors';
2
2
  import { analyze } from './analyze.js';
3
- import { label, score } from './output-utils.js';
3
+ import { score } from './output-utils.js';
4
+ /**
5
+ *
6
+ * @param list
7
+ * @param options
8
+ */
4
9
  export async function archaeologist(list, options) {
5
10
  const results = await analyze(list, options);
6
11
  const output = [];
@@ -9,9 +14,9 @@ export async function archaeologist(list, options) {
9
14
  for (const [sizeName, { image, dom }] of Object.entries(result.screenshots)) {
10
15
  if (image) {
11
16
  const { matches, file } = image;
12
- output.push(` ${label(sizeName)} ${score(matches, 0.9)} ${file}`);
17
+ output.push(` ${c.bgMagenta(` ${sizeName} `)} ${score(matches, 0.9)} ${file}`);
13
18
  }
14
- output.push(` ${label('HTML', c.bgBlueBright)}: ${score(dom.matches, 0.995)} ${dom.file}`);
19
+ output.push(` ${c.bgBlueBright(' HTML ')}: ${score(dom.matches, 0.995)} ${dom.file}`);
15
20
  }
16
21
  }
17
22
  process.stdout.write(output.join('\n') + '\n');
@@ -16,12 +16,18 @@ export type DiffImagesPhase = {
16
16
  };
17
17
  };
18
18
  type DiffImagesListener = (phase: keyof DiffImagesPhase, data: DiffImagesPhase[keyof DiffImagesPhase]) => void;
19
+ /**
20
+ *
21
+ * @param dataA
22
+ * @param dataB
23
+ * @param listener
24
+ */
19
25
  export declare function diffImages(dataA: Screenshot, dataB: Screenshot, listener: DiffImagesListener): Promise<{
20
26
  matches: number;
21
27
  images: {
22
- a: Buffer;
23
- b: Buffer;
24
- diff: Buffer;
28
+ a: Buffer<ArrayBufferLike>;
29
+ b: Buffer<ArrayBufferLike>;
30
+ diff: Buffer<ArrayBufferLike>;
25
31
  };
26
32
  } | null>;
27
33
  export {};
@@ -1,6 +1,12 @@
1
1
  import { Jimp, HorizontalAlign, VerticalAlign, JimpMime } from 'jimp';
2
2
  import pixelmatch from 'pixelmatch';
3
3
  import { PNG } from 'pngjs';
4
+ /**
5
+ *
6
+ * @param dataA
7
+ * @param dataB
8
+ * @param listener
9
+ */
4
10
  export async function diffImages(dataA, dataB, listener) {
5
11
  if (!dataA.binary || !dataB.binary) {
6
12
  return null;
@@ -31,6 +37,12 @@ export async function diffImages(dataA, dataB, listener) {
31
37
  },
32
38
  };
33
39
  }
40
+ /**
41
+ *
42
+ * @param bin
43
+ * @param width
44
+ * @param height
45
+ */
34
46
  async function resizeImg(bin, width, height) {
35
47
  const img = await Jimp.read(bin);
36
48
  img.contain({
@@ -1,3 +1,10 @@
1
+ /**
2
+ *
3
+ * @param urlA
4
+ * @param urlB
5
+ * @param dataA
6
+ * @param dataB
7
+ */
1
8
  export declare function diffTree(urlA: string, urlB: string, dataA: string, dataB: string): {
2
9
  changed: boolean;
3
10
  maxLine: number;
package/dist/diff-tree.js CHANGED
@@ -1,5 +1,12 @@
1
1
  import { createTwoFilesPatch } from 'diff';
2
2
  import parse from 'parse-diff';
3
+ /**
4
+ *
5
+ * @param urlA
6
+ * @param urlB
7
+ * @param dataA
8
+ * @param dataB
9
+ */
3
10
  export function diffTree(urlA, urlB, dataA, dataB) {
4
11
  const result = createTwoFilesPatch(urlA, urlB, dataA, dataB);
5
12
  const info = parse(result)[0];
@@ -0,0 +1,13 @@
1
+ import type { PageData } from './types.js';
2
+ import type { PageHook } from '@d-zero/puppeteer-screenshot';
3
+ export interface FreezeOptions {
4
+ readonly hooks: readonly PageHook[];
5
+ readonly limit?: number;
6
+ readonly debug?: boolean;
7
+ }
8
+ /**
9
+ *
10
+ * @param list
11
+ * @param options
12
+ */
13
+ export declare function freeze(list: readonly string[], options?: FreezeOptions): Promise<PageData[]>;
package/dist/freeze.js ADDED
@@ -0,0 +1,34 @@
1
+ import { mkdir } from 'node:fs/promises';
2
+ import path from 'node:path';
3
+ import { deal } from '@d-zero/puppeteer-dealer';
4
+ import { delay } from '@d-zero/shared/delay';
5
+ import c from 'ansi-colors';
6
+ import { analyzeUrlList } from './analize-url.js';
7
+ import { getData } from './get-data.js';
8
+ /**
9
+ *
10
+ * @param list
11
+ * @param options
12
+ */
13
+ export async function freeze(list, options) {
14
+ const results = [];
15
+ const dir = path.resolve(process.cwd(), '.archaeologist');
16
+ await mkdir(dir, { recursive: true }).catch(() => { });
17
+ const urlInfo = analyzeUrlList(list);
18
+ const useOldMode = urlInfo.hasAuth || urlInfo.hasNoSSL;
19
+ await deal(list.map((url) => ({ id: null, url })), (_, done, total) => {
20
+ return `${c.bold.magenta('🕵️ Archaeologist Freeze❄️')} ${done}/${total}`;
21
+ }, {
22
+ async deal(page, _, url, logger) {
23
+ const data = await getData(page, url, {
24
+ ...options,
25
+ }, logger);
26
+ await delay(600);
27
+ results.push(data);
28
+ },
29
+ }, {
30
+ ...options,
31
+ headless: useOldMode ? 'shell' : true,
32
+ });
33
+ return results;
34
+ }
@@ -1,8 +1,15 @@
1
1
  import type { PageData } from './types.js';
2
+ import type { Page } from '@d-zero/puppeteer-page';
2
3
  import type { PageHook } from '@d-zero/puppeteer-screenshot';
3
- import type { Page } from 'puppeteer';
4
4
  export interface GetDataOptions {
5
5
  readonly hooks?: readonly PageHook[];
6
6
  readonly htmlDiffOnly?: boolean;
7
7
  }
8
+ /**
9
+ *
10
+ * @param page
11
+ * @param url
12
+ * @param options
13
+ * @param update
14
+ */
8
15
  export declare function getData(page: Page, url: string, options: GetDataOptions, update: (log: string) => void): Promise<PageData>;
package/dist/get-data.js CHANGED
@@ -1,5 +1,12 @@
1
1
  import { distill } from '@d-zero/html-distiller';
2
2
  import { screenshotListener, screenshot } from '@d-zero/puppeteer-screenshot';
3
+ /**
4
+ *
5
+ * @param page
6
+ * @param url
7
+ * @param options
8
+ * @param update
9
+ */
3
10
  export async function getData(page, url, options, update) {
4
11
  const htmlDiffOnly = options.htmlDiffOnly ?? false;
5
12
  const screenshots = await screenshot(page, url, {
@@ -1,3 +1,6 @@
1
- import c from 'ansi-colors';
2
- export declare function label(str: string, color?: c.StyleFunction): string;
1
+ /**
2
+ *
3
+ * @param matches
4
+ * @param threshold
5
+ */
3
6
  export declare function score(matches: number, threshold: number): string;
@@ -1,7 +1,9 @@
1
1
  import c from 'ansi-colors';
2
- export function label(str, color = c.bgMagenta) {
3
- return color(` ${str} `);
4
- }
2
+ /**
3
+ *
4
+ * @param matches
5
+ * @param threshold
6
+ */
5
7
  export function score(matches, threshold) {
6
8
  const color = matches > threshold ? c.green : c.red;
7
9
  const num = (matches * 100).toFixed(1);
@@ -1,3 +1,7 @@
1
+ /**
2
+ *
3
+ * @param filePath
4
+ */
1
5
  export declare function readConfig(filePath: string): Promise<{
2
6
  pairList: [string, string][];
3
7
  hooks: import("@d-zero/puppeteer-page-scan").PageHook[];
@@ -3,6 +3,10 @@ import path from 'node:path';
3
3
  import { readPageHooks } from '@d-zero/puppeteer-page-scan';
4
4
  import { toList } from '@d-zero/readtext/list';
5
5
  import fm from 'front-matter';
6
+ /**
7
+ *
8
+ * @param filePath
9
+ */
6
10
  export async function readConfig(filePath) {
7
11
  const fileContent = await fs.readFile(filePath, 'utf8');
8
12
  const content =
package/dist/utils.d.ts CHANGED
@@ -1,2 +1,6 @@
1
- export declare function label(str: string): string;
1
+ /**
2
+ *
3
+ * @param matches
4
+ * @param threshold
5
+ */
2
6
  export declare function score(matches: number, threshold: number): string;
package/dist/utils.js CHANGED
@@ -1,7 +1,9 @@
1
1
  import c from 'ansi-colors';
2
- export function label(str) {
3
- return c.bgMagenta(` ${str} `);
4
- }
2
+ /**
3
+ *
4
+ * @param matches
5
+ * @param threshold
6
+ */
5
7
  export function score(matches, threshold) {
6
8
  const color = matches > threshold ? c.green : c.red;
7
9
  const num = (matches * 100).toFixed(1);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@d-zero/archaeologist",
3
- "version": "1.1.1",
3
+ "version": "1.1.3",
4
4
  "description": "Uncover visual and HTML differences in web pages with precision",
5
5
  "author": "D-ZERO",
6
6
  "license": "MIT",
@@ -23,28 +23,29 @@
23
23
  ],
24
24
  "scripts": {
25
25
  "build": "tsc",
26
+ "watch": "tsc --watch",
26
27
  "clean": "tsc --build --clean"
27
28
  },
28
29
  "dependencies": {
29
- "@d-zero/dealer": "1.1.0",
30
- "@d-zero/html-distiller": "1.0.0",
31
- "@d-zero/puppeteer-page-scan": "1.0.0",
32
- "@d-zero/puppeteer-screenshot": "1.2.0",
33
- "@d-zero/readtext": "1.1.0",
30
+ "@d-zero/html-distiller": "1.0.2",
31
+ "@d-zero/puppeteer-dealer": "0.3.0",
32
+ "@d-zero/puppeteer-page-scan": "3.0.0",
33
+ "@d-zero/puppeteer-screenshot": "3.0.0",
34
+ "@d-zero/readtext": "1.1.2",
34
35
  "ansi-colors": "4.1.3",
35
36
  "diff": "7.0.0",
36
37
  "front-matter": "4.0.2",
37
38
  "jimp": "1.6.0",
38
39
  "minimist": "1.2.8",
39
40
  "parse-diff": "0.11.1",
40
- "pixelmatch": "6.0.0",
41
- "pngjs": "7.0.0",
42
- "puppeteer": "23.5.0"
41
+ "pixelmatch": "7.1.0",
42
+ "pngjs": "7.0.0"
43
43
  },
44
44
  "devDependencies": {
45
- "@types/diff": "5.2.2",
45
+ "@d-zero/puppeteer-page": "0.3.0",
46
+ "@types/diff": "7.0.1",
46
47
  "@types/pixelmatch": "5.2.6",
47
48
  "@types/pngjs": "6.0.5"
48
49
  },
49
- "gitHead": "e8f65086bf7c316dda6667f1173da8585a5ef19c"
50
+ "gitHead": "e4fd17857e31022d121527b00fd7f009dbdb2142"
50
51
  }
@@ -1 +0,0 @@
1
- export declare function diffTreeColor(diff: string): string;
@@ -1,15 +0,0 @@
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
- }
@@ -1,2 +0,0 @@
1
- import type { PageHook } from '@d-zero/puppeteer-screenshot';
2
- export declare function readHooks(hooks: readonly string[], listfile: string): Promise<PageHook[]>;
@@ -1,20 +0,0 @@
1
- import path from 'node:path';
2
- export async function readHooks(hooks, listfile) {
3
- const listfileDir = path.dirname(listfile);
4
- const pageHooks = await Promise.all(hooks.map(async (hook) => {
5
- const hookAbsPath = path.isAbsolute(hook) ? hook : path.resolve(listfileDir, hook);
6
- const { default: mod } = await import(hookAbsPath).catch((error) => {
7
- if (error instanceof Error &&
8
- 'code' in error &&
9
- error.code === 'ERR_MODULE_NOT_FOUND') {
10
- throw new Error(`Hook: ${hook} not found`, { cause: error });
11
- }
12
- throw error;
13
- });
14
- if (typeof mod !== 'function') {
15
- throw new TypeError(`Hook ${hook} is not a function`);
16
- }
17
- return mod;
18
- }));
19
- return pageHooks;
20
- }