meodp 0.0.1

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.
@@ -0,0 +1,862 @@
1
+ 'use strict';
2
+
3
+ const consola = require('consola');
4
+ const PQueue = require('p-queue');
5
+ const utils = require('consola/utils');
6
+ const playwright = require('playwright');
7
+ const path = require('node:path');
8
+ const process = require('node:process');
9
+ const dateFns = require('date-fns');
10
+ const fs = require('fs-extra');
11
+ const stripAnsi = require('strip-ansi');
12
+ const winston = require('winston');
13
+ const transports = require('winston/lib/winston/transports');
14
+ const cliProgress = require('cli-progress');
15
+ const Options = require('cli-progress/lib/options');
16
+ const _defaultFormatBar = require('cli-progress/lib/format-bar');
17
+ const _defaultFormatTime = require('cli-progress/lib/format-time');
18
+
19
+ function _interopDefaultCompat (e) { return e && typeof e === 'object' && 'default' in e ? e.default : e; }
20
+
21
+ const consola__default = /*#__PURE__*/_interopDefaultCompat(consola);
22
+ const PQueue__default = /*#__PURE__*/_interopDefaultCompat(PQueue);
23
+ const path__default = /*#__PURE__*/_interopDefaultCompat(path);
24
+ const process__default = /*#__PURE__*/_interopDefaultCompat(process);
25
+ const fs__default = /*#__PURE__*/_interopDefaultCompat(fs);
26
+ const stripAnsi__default = /*#__PURE__*/_interopDefaultCompat(stripAnsi);
27
+ const winston__default = /*#__PURE__*/_interopDefaultCompat(winston);
28
+ const transports__default = /*#__PURE__*/_interopDefaultCompat(transports);
29
+ const cliProgress__default = /*#__PURE__*/_interopDefaultCompat(cliProgress);
30
+ const Options__default = /*#__PURE__*/_interopDefaultCompat(Options);
31
+ const _defaultFormatBar__default = /*#__PURE__*/_interopDefaultCompat(_defaultFormatBar);
32
+ const _defaultFormatTime__default = /*#__PURE__*/_interopDefaultCompat(_defaultFormatTime);
33
+
34
+ const siteUrlMap = /* @__PURE__ */ new Map();
35
+ const PQueueMap = /* @__PURE__ */ new Map();
36
+
37
+ var __defProp$1 = Object.defineProperty;
38
+ var __defNormalProp$1 = (obj, key, value) => key in obj ? __defProp$1(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
39
+ var __publicField$1 = (obj, key, value) => {
40
+ __defNormalProp$1(obj, typeof key !== "symbol" ? key + "" : key, value);
41
+ return value;
42
+ };
43
+ function logUrlItemInfo(urlItem) {
44
+ consola__default.info("urlItem", urlItem);
45
+ }
46
+ const _LocalLog = class _LocalLog {
47
+ constructor(options = {}) {
48
+ _LocalLog.options = options;
49
+ _LocalLog.nameRule = options.nameRule || "url";
50
+ _LocalLog.init(options);
51
+ }
52
+ /**
53
+ * @desc init ensure dir
54
+ */
55
+ static async init(options) {
56
+ const cwd = options?.cwd || process__default.cwd();
57
+ _LocalLog.rootLogFolder = path__default.resolve(cwd, "logs/meodp");
58
+ fs__default.ensureDirSync(this.rootLogFolder);
59
+ if (_LocalLog.nameRule === "timestamp") {
60
+ const now = dateFns.formatDate(/* @__PURE__ */ new Date(), "yyyy-MM-dd-HH-mm-ss");
61
+ this.logFolder = path__default.resolve(this.rootLogFolder, now);
62
+ } else {
63
+ this.logFolder = path__default.resolve(this.rootLogFolder);
64
+ }
65
+ fs__default.ensureDirSync(this.logFolder);
66
+ this.siteLogFolder = path__default.resolve(this.logFolder, "site");
67
+ fs__default.ensureDirSync(this.siteLogFolder);
68
+ this.linkLogFile = path__default.resolve(this.logFolder, "link.log");
69
+ }
70
+ /**
71
+ * @desc clean log
72
+ */
73
+ static async clean() {
74
+ await fs__default.remove(this.rootLogFolder);
75
+ }
76
+ static async log(filePath, ...args) {
77
+ const now = dateFns.formatDate(/* @__PURE__ */ new Date(), "yyyy-MM-dd HH:mm:ss");
78
+ const content = stripAnsi__default(`${[now, ...args].join(" ")}
79
+ `);
80
+ await fs__default.appendFile(filePath, content);
81
+ }
82
+ /**
83
+ * @desc append log
84
+ */
85
+ static async SiteLog(urlItem, ...args) {
86
+ const logName = (urlItem.name || urlItem.url).replace("https://", "").replace("http://", "").replace(/\//g, "-");
87
+ const logPath = path__default.resolve(this.logFolder, urlItem.type, `${logName}.log`);
88
+ await _LocalLog.log(logPath, ...args);
89
+ }
90
+ /**
91
+ * console link log
92
+ */
93
+ static async linkLog(...args) {
94
+ await _LocalLog.log(this.linkLogFile, ...args);
95
+ }
96
+ static async logByType(urlItem, ...args) {
97
+ switch (urlItem.type) {
98
+ case "site":
99
+ this.SiteLog(urlItem, ...args);
100
+ break;
101
+ case "link":
102
+ this.linkLog(...args);
103
+ break;
104
+ }
105
+ }
106
+ /**
107
+ * @desc create log
108
+ */
109
+ static createLog(urlItem) {
110
+ return (...args) => {
111
+ _LocalLog.logByType(urlItem, ...args);
112
+ };
113
+ }
114
+ };
115
+ /**
116
+ * 日志根目录
117
+ */
118
+ __publicField$1(_LocalLog, "rootLogFolder");
119
+ /**
120
+ * rootLogFolder + yyyy-MM-dd-HH-mm-ss
121
+ */
122
+ __publicField$1(_LocalLog, "logFolder");
123
+ /**
124
+ * rootLogFolder + site
125
+ */
126
+ __publicField$1(_LocalLog, "siteLogFolder");
127
+ /**
128
+ * link.log
129
+ */
130
+ __publicField$1(_LocalLog, "linkLogFile");
131
+ /**
132
+ * 日志命名规则
133
+ * - timestamp: yyyy-MM-dd-HH-mm-ss 每次都会建立一个以时间戳命名的文件夹
134
+ * - url: url 在同一个文件夹下
135
+ */
136
+ __publicField$1(_LocalLog, "nameRule");
137
+ __publicField$1(_LocalLog, "options");
138
+ let LocalLog = _LocalLog;
139
+ const localLog = new LocalLog();
140
+
141
+ const RAW_SYMBOLS = {
142
+ block: "\u2503",
143
+ start: "\u25D0",
144
+ pointer: "\u276F",
145
+ uncheck: "\u25CC",
146
+ success: "\u2714",
147
+ info: "\u2139",
148
+ warn: "\u26A0",
149
+ error: "\u2716",
150
+ line: "\u2502",
151
+ end: "\u2514"
152
+ };
153
+ const COLORFUL_SYMBOLS = {
154
+ block: utils.colors.bold(utils.colors.dim(RAW_SYMBOLS.block)),
155
+ start: utils.colors.magenta(RAW_SYMBOLS.start),
156
+ uncheck: utils.colors.gray(RAW_SYMBOLS.uncheck),
157
+ pointer: utils.colors.cyan(RAW_SYMBOLS.pointer),
158
+ success: utils.colors.green(RAW_SYMBOLS.success),
159
+ info: utils.colors.blue(RAW_SYMBOLS.info),
160
+ warn: utils.colors.yellow(RAW_SYMBOLS.warn),
161
+ error: utils.colors.red(RAW_SYMBOLS.error),
162
+ line: utils.colors.dim(RAW_SYMBOLS.line)
163
+ };
164
+
165
+ const formatter = (options, params, payload) => {
166
+ const { value, total } = params;
167
+ const formatTime = options.formatTime || _defaultFormatTime__default;
168
+ const formatBar = options.formatBar || _defaultFormatBar__default;
169
+ const bar = formatBar(params.progress, options);
170
+ const stopTime = params.stopTime || Date.now();
171
+ const elapsedTime = Math.round((stopTime - params.startTime) / 1e3);
172
+ const duration_formatted = formatTime(elapsedTime, options, 1);
173
+ consola__default.debug(duration_formatted);
174
+ if (!payload.type) {
175
+ options = Object.assign({}, options, cliProgress__default.Presets.rect);
176
+ Options__default.assignDerivedOptions(options);
177
+ const bar2 = formatBar(params.progress, options);
178
+ const { site } = payload;
179
+ const url = payload.url || "";
180
+ return `${bar2} ${value}/${total} | ${utils.colors.yellow(site || "MEODP Item")} | ${utils.colors.cyan(url || "URL")}`;
181
+ } else {
182
+ const { error_count, emoji, url, type } = payload;
183
+ const errorInfo = error_count ? `\u274C${error_count} ` : "";
184
+ return `${bar} \u2705${utils.colors.green(value)}/${total} ${errorInfo}| ${duration_formatted} | ${emoji} ${utils.colors.yellow(type)} | ${utils.colors.cyan(url)}`;
185
+ }
186
+ };
187
+ const multiBar = new cliProgress__default.MultiBar({
188
+ hideCursor: true,
189
+ // format: '{bar} {percentage}% | ✅{value}/{total} ❌{error_count} | {duration_formatted} | {name}',
190
+ format: formatter,
191
+ barsize: 20,
192
+ autopadding: true,
193
+ barCompleteChar: "\u2588",
194
+ barIncompleteChar: "\u2591",
195
+ // important! redraw everything to avoid "empty" completed bars
196
+ forceRedraw: true
197
+ }, cliProgress__default.Presets.rect);
198
+ const progressBarMap = /* @__PURE__ */ new Map();
199
+
200
+ function formatArgs(args) {
201
+ return args.join(" ");
202
+ }
203
+ function getFormattedDataFromResponse(res) {
204
+ const statusCode = res?.status();
205
+ const statusText = res?.statusText();
206
+ const statusInfo = statusText ? `${statusCode?.toString()} ${statusText}` : statusCode?.toString();
207
+ const req = res.request();
208
+ const duration = req.timing().responseEnd - req.timing().requestStart;
209
+ const durationText = utils.colors.dim(`\u2502${utils.colors.blue(`${duration.toString().padStart(4, " ")}${utils.colors.white("ms")}`)} \u2502`);
210
+ return {
211
+ duration,
212
+ durationText,
213
+ statusCode,
214
+ statusText,
215
+ statusInfo,
216
+ linkText: utils.colors.dim(res.url())
217
+ };
218
+ }
219
+
220
+ function registerPageEvents(page, urlItem) {
221
+ const urlMap = /* @__PURE__ */ new Map();
222
+ const log = LocalLog.createLog(urlItem);
223
+ let respondNum = 0;
224
+ const curBar = progressBarMap.get("CURRENT");
225
+ page.on("request", (request) => {
226
+ if (urlMap.size === 0) {
227
+ curBar?.start(1, 0);
228
+ }
229
+ const requestUrl = request.url();
230
+ if (MEODP.isIgnoredLink(requestUrl)) {
231
+ urlMap.set(requestUrl, {
232
+ ignored: true,
233
+ request
234
+ });
235
+ return;
236
+ }
237
+ if (requestUrl && !urlMap.has(requestUrl)) {
238
+ urlMap.set(requestUrl, {
239
+ request
240
+ });
241
+ }
242
+ curBar?.setTotal(urlMap.size);
243
+ });
244
+ page.on("response", (response) => {
245
+ const request = response.request();
246
+ const item = urlMap.get(response.url());
247
+ if (!item || item.ignored) {
248
+ return;
249
+ }
250
+ urlMap.set(response.url(), {
251
+ request,
252
+ response
253
+ });
254
+ const urlInfo = urlMap.get(response.url());
255
+ if (!urlInfo)
256
+ return;
257
+ if (urlInfo.response)
258
+ return;
259
+ respondNum += 1;
260
+ const {
261
+ durationText,
262
+ statusInfo,
263
+ linkText
264
+ } = getFormattedDataFromResponse(response);
265
+ const info = ` [${statusInfo}] ${durationText} ${linkText}`;
266
+ if (response.status() >= 400) {
267
+ MEODP.logger.error(info);
268
+ } else {
269
+ MEODP.logger.info(info);
270
+ }
271
+ log(urlItem.type === "link" ? info.trim() : info);
272
+ curBar?.update(respondNum, {
273
+ site: urlItem.url,
274
+ url: response.url()
275
+ });
276
+ });
277
+ page.on("requestfailed", (request) => {
278
+ const url = request.url();
279
+ const urlInfo = urlMap.get(url);
280
+ if (!urlInfo?.ignored) {
281
+ urlMap.set(url, {
282
+ request,
283
+ failed: true
284
+ });
285
+ }
286
+ });
287
+ return urlMap;
288
+ }
289
+
290
+ function isLink(url) {
291
+ if (url) {
292
+ return url.startsWith("http") || url.startsWith("/");
293
+ } else {
294
+ return false;
295
+ }
296
+ }
297
+
298
+ function parseUrlMap(urlMap) {
299
+ const successCount = Array.from(urlMap.values()).filter(
300
+ (value) => value.response && value.response.status() && value.response.status() < 400
301
+ ).length;
302
+ const timeoutCount = Array.from(urlMap.values()).filter((value) => !value.response && !value.ignored).length;
303
+ const timeoutTxt = timeoutCount ? utils.colors.redBright(utils.colors.redBright(`(\u{1F6AB} ${timeoutCount} Timeout)`)) : "";
304
+ const failedCount = Array.from(urlMap.values()).filter((value) => {
305
+ return value.failed || value.response && value.response?.status() > 400;
306
+ }).length;
307
+ const ignoredCount = Array.from(urlMap.values()).filter((value) => value.ignored).length;
308
+ const success = {
309
+ count: successCount,
310
+ text: `${COLORFUL_SYMBOLS.success} ${utils.colors.green(`${successCount} OK`)}`
311
+ };
312
+ const timeout = {
313
+ count: timeoutCount,
314
+ text: timeoutTxt
315
+ };
316
+ const failedTxt = `${COLORFUL_SYMBOLS.error} ${failedCount} Failed`;
317
+ const failed = {
318
+ count: failedCount,
319
+ text: `${failedCount ? utils.colors.red(failedTxt) : utils.colors.dim(failedTxt)}`
320
+ };
321
+ const ignored = {
322
+ count: ignoredCount,
323
+ text: utils.colors.dim(`\u{1F47B} ${ignoredCount} Ignored`)
324
+ };
325
+ const total = {
326
+ count: urlMap.size,
327
+ text: `${urlMap.size} Total Requests`
328
+ };
329
+ return {
330
+ success,
331
+ failed,
332
+ /**
333
+ * Ignored links, controlled by `ignoreLinks` and `ignoreExtensions`
334
+ */
335
+ ignored,
336
+ timeout,
337
+ total
338
+ };
339
+ }
340
+
341
+ const emojiMap = {
342
+ site: "\u{1F3E0}",
343
+ link: "\u{1F517}",
344
+ sitemap: "\u{1F4C4}"
345
+ };
346
+ function getMEODPUrlItemInfo(urlItem) {
347
+ const type = typeof urlItem === "string" ? "link" : urlItem.type;
348
+ const url = typeof urlItem === "string" ? urlItem : urlItem.url;
349
+ const emoji = emojiMap[type] || "\u{1F517}";
350
+ return {
351
+ type,
352
+ url,
353
+ emoji
354
+ };
355
+ }
356
+
357
+ function createMEODPLogger() {
358
+ return {
359
+ _log: (...args) => {
360
+ const allLogPath = path__default.resolve(MEODP.logFolder, "all.log");
361
+ LocalLog.log(allLogPath, stripAnsi__default(args.join(" ")));
362
+ },
363
+ log: (...args) => {
364
+ const content = formatArgs(args);
365
+ consola__default.log(content);
366
+ MEODP.logger._log(content);
367
+ },
368
+ inner: (...args) => {
369
+ const content = formatArgs(args);
370
+ consola__default.log(COLORFUL_SYMBOLS.line, " ", content);
371
+ MEODP.logger._log(COLORFUL_SYMBOLS.line, " ", content);
372
+ },
373
+ start: (...args) => {
374
+ const content = formatArgs(args);
375
+ consola__default.start(content);
376
+ MEODP.logger._log(COLORFUL_SYMBOLS.start, content);
377
+ },
378
+ success: (...args) => {
379
+ const content = formatArgs(args);
380
+ consola__default.success(content);
381
+ MEODP.logger._log(COLORFUL_SYMBOLS.success, content);
382
+ },
383
+ info: (...args) => {
384
+ const content = formatArgs(args);
385
+ consola__default.info(content);
386
+ MEODP.logger._log(COLORFUL_SYMBOLS.info, content);
387
+ },
388
+ warn: (...args) => {
389
+ const content = formatArgs(args);
390
+ consola__default.warn(content);
391
+ MEODP.logger._log(COLORFUL_SYMBOLS.warn, content);
392
+ },
393
+ error: (...args) => {
394
+ const content = formatArgs(args);
395
+ consola__default.log(COLORFUL_SYMBOLS.error, content);
396
+ MEODP.logger._log(COLORFUL_SYMBOLS.error, content);
397
+ MEODP.wLogger.error(content);
398
+ }
399
+ };
400
+ }
401
+
402
+ function createWinstonLogger() {
403
+ const allLogPath = path__default.resolve(MEODP.logFolder, "all.log");
404
+ const errorLogPath = path__default.resolve(MEODP.logFolder, "error.log");
405
+ const customFormats = [
406
+ winston__default.format.timestamp({
407
+ format: "YYYY-MM-DD HH:mm:ss"
408
+ })
409
+ // winston.format.colorize(),
410
+ // winston.format.errors({ stack: true }),
411
+ // winston.format.json(),
412
+ // winston.format.simple(),
413
+ // winston.format.uncolorize(),
414
+ ];
415
+ customFormats.push(
416
+ winston__default.format.printf(({ level, message, timestamp, service }) => {
417
+ const content = [
418
+ timestamp,
419
+ `[${service}]`,
420
+ level.toUpperCase(),
421
+ message
422
+ ];
423
+ return content.join(" ");
424
+ })
425
+ );
426
+ const customTransports = [
427
+ new transports__default.File({
428
+ filename: errorLogPath,
429
+ level: "error"
430
+ }),
431
+ new transports__default.File({
432
+ filename: allLogPath
433
+ // level: 'info',
434
+ })
435
+ ];
436
+ return winston__default.createLogger({
437
+ level: "info",
438
+ format: winston__default.format.combine(...customFormats),
439
+ defaultMeta: { service: "MEODP" },
440
+ transports: customTransports,
441
+ exitOnError: false
442
+ });
443
+ }
444
+
445
+ var __defProp = Object.defineProperty;
446
+ var __defNormalProp = (obj, key, value) => key in obj ? __defProp(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
447
+ var __publicField = (obj, key, value) => {
448
+ __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
449
+ return value;
450
+ };
451
+ const _MEODP = class _MEODP {
452
+ constructor(config) {
453
+ _MEODP.config = config;
454
+ }
455
+ static async init(config) {
456
+ _MEODP.config = config;
457
+ if (config.clean) {
458
+ console.log();
459
+ consola__default.start("Cleaning history log...");
460
+ await LocalLog.clean();
461
+ consola__default.success("Clean history log.");
462
+ console.log();
463
+ }
464
+ await LocalLog.init();
465
+ _MEODP.logFolder = LocalLog.logFolder || path__default.resolve(process__default.cwd(), "logs/meodp");
466
+ _MEODP.wLogger = createWinstonLogger();
467
+ }
468
+ /**
469
+ * filter link by ignoreLinks
470
+ */
471
+ static isIgnoredLink(link) {
472
+ const isIgnoredLinks = _MEODP.config.ignoreLinks?.some((ignoreLink) => {
473
+ if (ignoreLink instanceof RegExp) {
474
+ return ignoreLink.test(link);
475
+ } else if (link.startsWith(ignoreLink)) {
476
+ return true;
477
+ }
478
+ return false;
479
+ });
480
+ const isIgnoredExtensions = _MEODP.config.ignoreExtensions?.some((ext) => link.endsWith(ext));
481
+ return isIgnoredLinks || isIgnoredExtensions;
482
+ }
483
+ /**
484
+ * @desc 是否为外链
485
+ */
486
+ static isExternalLink(url, urlItem) {
487
+ return url.startsWith("http") && !url.startsWith(urlItem.url);
488
+ }
489
+ /**
490
+ * @desc 通用日志
491
+ * - 本地日志
492
+ * - winston 日志
493
+ */
494
+ static log(urlItem, ...args) {
495
+ _MEODP.logger.info(args);
496
+ LocalLog.logByType(urlItem, args.join(" "));
497
+ }
498
+ };
499
+ /**
500
+ * global config
501
+ */
502
+ __publicField(_MEODP, "config");
503
+ __publicField(_MEODP, "configFile");
504
+ /**
505
+ * browser instance by playwright
506
+ */
507
+ __publicField(_MEODP, "browser");
508
+ /**
509
+ * log folder
510
+ * logs/meodp/
511
+ */
512
+ __publicField(_MEODP, "logFolder");
513
+ __publicField(_MEODP, "logger", createMEODPLogger());
514
+ __publicField(_MEODP, "wLogger");
515
+ let MEODP = _MEODP;
516
+
517
+ async function getBrowser() {
518
+ if (!MEODP.browser) {
519
+ MEODP.browser = await playwright.chromium.launch({
520
+ // 15s
521
+ timeout: 15e3
522
+ });
523
+ }
524
+ return MEODP.browser;
525
+ }
526
+
527
+ async function getSiteLinks(page, options) {
528
+ const links = await page.$$eval("a", (elements) => {
529
+ return elements.map((element) => element.getAttribute("href")).filter(Boolean);
530
+ });
531
+ const filterLinks = links.map((link) => {
532
+ if (!isLink(link)) {
533
+ return false;
534
+ }
535
+ const isExternalLink = MEODP.isExternalLink(link, options.urlItem);
536
+ if (!isExternalLink) {
537
+ {
538
+ const url = new URL(link, page.url());
539
+ url.search = "";
540
+ return url.toString();
541
+ }
542
+ } else {
543
+ return link;
544
+ }
545
+ }).filter(Boolean);
546
+ return filterLinks;
547
+ }
548
+ async function checkUrlNomoduleAssets(page) {
549
+ const legacyScripts = await page.$$eval("script", (elements) => {
550
+ return elements.filter((element) => element.getAttribute("nomodule") && element.getAttribute("src")).map((el) => el.getAttribute("src"));
551
+ }).catch((e) => {
552
+ consola__default.error(e);
553
+ }) || [];
554
+ if (!legacyScripts.length)
555
+ return true;
556
+ const browser = await getBrowser();
557
+ const context = await browser.newContext();
558
+ const loadPromises = legacyScripts.map((nomoduleUrl) => {
559
+ return (async () => {
560
+ if (!nomoduleUrl) {
561
+ return;
562
+ }
563
+ const startTime = Date.now();
564
+ const newPage = await context.newPage();
565
+ const response = await newPage.goto(nomoduleUrl);
566
+ if (!response) {
567
+ return;
568
+ }
569
+ const {
570
+ statusCode,
571
+ statusInfo,
572
+ linkText
573
+ } = getFormattedDataFromResponse(response);
574
+ const duration = Date.now() - startTime;
575
+ const durationTxt = utils.colors.dim(`\u2502${utils.colors.blue(`${duration.toString().padStart(4, " ")}${utils.colors.white("ms")}`)} \u2502`);
576
+ if (statusCode >= 400) {
577
+ MEODP.logger.inner(" ", COLORFUL_SYMBOLS.error, utils.colors.red(statusInfo), durationTxt, linkText);
578
+ } else {
579
+ MEODP.logger.inner(" ", COLORFUL_SYMBOLS.success, utils.colors.green(statusInfo), durationTxt, linkText);
580
+ }
581
+ await newPage.close();
582
+ return statusCode ? statusCode < 400 : false;
583
+ })();
584
+ });
585
+ const results = await Promise.all(loadPromises);
586
+ return results.every((result) => result);
587
+ }
588
+ async function checkSiteUrl(url, options) {
589
+ const browser = await getBrowser();
590
+ const page = await browser.newPage();
591
+ const bar = progressBarMap.get(options.urlItem.url);
592
+ const startTime = Date.now();
593
+ const isExternalLink = MEODP.isExternalLink(url, options.urlItem);
594
+ const log = LocalLog.createLog(options.urlItem);
595
+ if (!MEODP.config.checkExternalLinks && isExternalLink) {
596
+ let res = null;
597
+ try {
598
+ res = await page.goto(url, {
599
+ waitUntil: "load"
600
+ });
601
+ } catch (e) {
602
+ MEODP.logger.error(e);
603
+ }
604
+ if (!res)
605
+ return;
606
+ const statusCode = res?.status() || 0;
607
+ const checkStatus = statusCode < 400 ? "passed" : "failed";
608
+ siteUrlMap.set(url, {
609
+ response: res,
610
+ statusCode,
611
+ checkStatus
612
+ });
613
+ MEODP.logger.log(COLORFUL_SYMBOLS.line, " ", utils.colors.green(`[${statusCode}]`), utils.colors.cyan(url), utils.colors.dim(`(in ${((Date.now() - startTime) / 1e3).toFixed(2)}s)`));
614
+ return;
615
+ }
616
+ const urlMap = registerPageEvents(page, options.urlItem);
617
+ try {
618
+ const res = await page.goto(url, {
619
+ waitUntil: "networkidle"
620
+ });
621
+ const statusCode = res?.status() || 0;
622
+ siteUrlMap.set(url, {
623
+ response: res,
624
+ statusCode,
625
+ checkStatus: "goto"
626
+ });
627
+ const { success, failed, total, ignored, timeout } = parseUrlMap(urlMap);
628
+ const duration = (Date.now() - startTime) / 1e3;
629
+ const statusText = res?.statusText();
630
+ const logInfo = [
631
+ COLORFUL_SYMBOLS.line,
632
+ " ",
633
+ utils.colors.green(`[${statusCode}${statusText ? ` ${statusText}` : ""}]`),
634
+ utils.colors.cyan(url),
635
+ utils.colors.dim(`(in ${duration}s)`),
636
+ total.text,
637
+ success.text,
638
+ failed.text,
639
+ timeout.text,
640
+ ignored.text
641
+ ];
642
+ MEODP.logger.log(...logInfo);
643
+ log(...logInfo);
644
+ log();
645
+ if (MEODP.config.log?.type === "progress") {
646
+ bar?.update(success.count, {
647
+ value: utils.colors.green(success.count),
648
+ error_count: failed.count ? utils.colors.red(failed.count) : 0
649
+ });
650
+ } else {
651
+ }
652
+ } catch (e) {
653
+ MEODP.logger.error(e);
654
+ }
655
+ for (const [url2, info] of urlMap) {
656
+ if (!info.response && !info.ignored) {
657
+ MEODP.logger.log(COLORFUL_SYMBOLS.line, " ", COLORFUL_SYMBOLS.error, `Timeout: ${utils.colors.underline(url2)}`);
658
+ }
659
+ }
660
+ let noModuleAssetsPassed = true;
661
+ if (options.checkNoModule) {
662
+ noModuleAssetsPassed = await checkUrlNomoduleAssets(page);
663
+ }
664
+ const urlMapValues = Array.from(urlMap.values());
665
+ const siteUrlItem = siteUrlMap.get(url);
666
+ if (siteUrlItem) {
667
+ if (urlMapValues.every((value) => value.response?.status() && value.response?.status() < 400) && noModuleAssetsPassed) {
668
+ siteUrlItem.checkStatus = "passed";
669
+ const successCount = Array.from(siteUrlMap.values()).filter((value) => value.checkStatus === "passed").length;
670
+ bar?.update(successCount);
671
+ } else {
672
+ siteUrlItem.checkStatus = "failed";
673
+ }
674
+ }
675
+ if (!isExternalLink) {
676
+ const queue = PQueueMap.get(url);
677
+ if (queue) {
678
+ const links = await getSiteLinks(page, options);
679
+ for (const link of links) {
680
+ if (!siteUrlMap.has(link)) {
681
+ siteUrlMap.set(link, {
682
+ statusCode: 0,
683
+ checkStatus: "pending"
684
+ });
685
+ bar?.setTotal(siteUrlMap.size);
686
+ const url2 = new URL(link, page.url()).href;
687
+ await queue.add(async () => {
688
+ await checkSiteUrl(url2, options);
689
+ });
690
+ }
691
+ }
692
+ }
693
+ }
694
+ await page.close();
695
+ }
696
+ async function checkSite(props) {
697
+ const { url } = props;
698
+ const queue = new PQueue__default({
699
+ concurrency: MEODP.config.concurrency
700
+ });
701
+ PQueueMap.set(url, queue);
702
+ const bar = progressBarMap.get(url);
703
+ const browser = await getBrowser();
704
+ const context = await browser.newContext();
705
+ const page = await context.newPage();
706
+ const homeUrl = url;
707
+ siteUrlMap.set(homeUrl, {
708
+ statusCode: 0,
709
+ checkStatus: "pending"
710
+ });
711
+ bar?.setTotal(siteUrlMap.size);
712
+ await checkSiteUrl(homeUrl, {
713
+ urlItem: props,
714
+ checkNoModule: true
715
+ });
716
+ await queue.onIdle();
717
+ await page.close();
718
+ await context.close();
719
+ }
720
+
721
+ async function checkLink(urlItem) {
722
+ const browser = await getBrowser();
723
+ const context = await browser.newContext();
724
+ const page = await context.newPage();
725
+ const startTime = Date.now();
726
+ const { url } = urlItem;
727
+ const bar = progressBarMap.get(url);
728
+ const urlMap = registerPageEvents(page, urlItem);
729
+ const res = await page.goto(url, {
730
+ waitUntil: "networkidle"
731
+ });
732
+ const isHtml = res?.headers()["content-type"].includes("text/html");
733
+ if (isHtml) {
734
+ const { success, failed, total, ignored, timeout } = parseUrlMap(urlMap);
735
+ const duration = (Date.now() - startTime) / 1e3;
736
+ const statusCode = res?.status() || 0;
737
+ const statusText = res?.statusText();
738
+ const logInfo = [
739
+ COLORFUL_SYMBOLS.line,
740
+ " ",
741
+ utils.colors.green(`[${statusCode}${statusText ? ` ${statusText}` : ""}]`),
742
+ utils.colors.cyan(url),
743
+ total.text,
744
+ utils.colors.dim(`(in ${duration}s)`),
745
+ success.text,
746
+ failed.text,
747
+ timeout.text,
748
+ ignored.text
749
+ ];
750
+ MEODP.logger.log(...logInfo);
751
+ bar?.setTotal(urlMap.size);
752
+ bar?.update(success.count);
753
+ } else {
754
+ bar?.update(1);
755
+ }
756
+ await checkUrlNomoduleAssets(page);
757
+ await page.close();
758
+ await context.close();
759
+ }
760
+
761
+ async function checkMEODPUrl(meodpUrl) {
762
+ if (typeof meodpUrl === "string") {
763
+ meodpUrl = {
764
+ type: "link",
765
+ url: meodpUrl
766
+ };
767
+ }
768
+ const { type, url, emoji } = getMEODPUrlItemInfo(meodpUrl);
769
+ MEODP.logger.log();
770
+ MEODP.logger.start(`${emoji} ${utils.colors.yellow(`[${type}]`)} ${utils.colors.cyan(url)}`);
771
+ const startTime = Date.now();
772
+ if (typeof meodpUrl === "object") {
773
+ switch (meodpUrl.type) {
774
+ case "site":
775
+ await checkSite(meodpUrl);
776
+ break;
777
+ case "link":
778
+ await checkLink(meodpUrl);
779
+ break;
780
+ }
781
+ }
782
+ const duration = Date.now() - startTime;
783
+ const bar = progressBarMap.get(meodpUrl.url);
784
+ bar?.stop();
785
+ MEODP.logger.success(`Done in ${duration / 1e3}s.`);
786
+ }
787
+
788
+ const defaultMEODPConfig = {
789
+ urls: [],
790
+ concurrency: 10,
791
+ log: {
792
+ type: "raw",
793
+ file: true
794
+ }
795
+ };
796
+ function defineConfig(config) {
797
+ return config;
798
+ }
799
+ async function runByConfig(config) {
800
+ const { urls, debug } = config;
801
+ consola__default.level = debug ? 5 : 3;
802
+ const queue = new PQueue__default({
803
+ concurrency: config.concurrency
804
+ });
805
+ if (config.log?.type === "progress") {
806
+ for (const urlItem of urls) {
807
+ const { type, url, emoji } = getMEODPUrlItemInfo(urlItem);
808
+ const bar = multiBar.create(1, 0, {
809
+ emoji,
810
+ url,
811
+ type,
812
+ error_count: 0
813
+ });
814
+ progressBarMap.set(url, bar);
815
+ }
816
+ const curBar = multiBar.create(1, 0, {
817
+ name: "CURRENT"
818
+ });
819
+ progressBarMap.set("CURRENT", curBar);
820
+ }
821
+ const browser = await getBrowser();
822
+ for (const urlItem of urls) {
823
+ await queue.add(async () => {
824
+ await checkMEODPUrl(urlItem);
825
+ });
826
+ }
827
+ await queue.onIdle();
828
+ if (config.log?.type === "progress") {
829
+ multiBar.stop();
830
+ }
831
+ await browser.close();
832
+ consola__default.debug("browser closed");
833
+ return true;
834
+ }
835
+
836
+ exports.LocalLog = LocalLog;
837
+ exports.MEODP = MEODP;
838
+ exports.PQueueMap = PQueueMap;
839
+ exports.checkLink = checkLink;
840
+ exports.checkMEODPUrl = checkMEODPUrl;
841
+ exports.checkSite = checkSite;
842
+ exports.checkSiteUrl = checkSiteUrl;
843
+ exports.checkUrlNomoduleAssets = checkUrlNomoduleAssets;
844
+ exports.createMEODPLogger = createMEODPLogger;
845
+ exports.createWinstonLogger = createWinstonLogger;
846
+ exports.defaultMEODPConfig = defaultMEODPConfig;
847
+ exports.defineConfig = defineConfig;
848
+ exports.emojiMap = emojiMap;
849
+ exports.formatArgs = formatArgs;
850
+ exports.getBrowser = getBrowser;
851
+ exports.getFormattedDataFromResponse = getFormattedDataFromResponse;
852
+ exports.getMEODPUrlItemInfo = getMEODPUrlItemInfo;
853
+ exports.getSiteLinks = getSiteLinks;
854
+ exports.isLink = isLink;
855
+ exports.localLog = localLog;
856
+ exports.logUrlItemInfo = logUrlItemInfo;
857
+ exports.multiBar = multiBar;
858
+ exports.parseUrlMap = parseUrlMap;
859
+ exports.progressBarMap = progressBarMap;
860
+ exports.registerPageEvents = registerPageEvents;
861
+ exports.runByConfig = runByConfig;
862
+ exports.siteUrlMap = siteUrlMap;