403Webshell
Server IP : 35.80.110.71  /  Your IP : 216.73.216.221
Web Server : Apache/2.4.58 (Ubuntu)
System : Linux ip-172-31-21-44 6.17.0-1019-aws #19~24.04.1-Ubuntu SMP Tue Jun 23 18:53:06 UTC 2026 x86_64
User : ubuntu ( 1000)
PHP Version : 8.3.31
Disable Function : NONE
MySQL : OFF  |  cURL : ON  |  WGET : ON  |  Perl : ON  |  Python : OFF  |  Sudo : ON  |  Pkexec : OFF
Directory :  /usr/lib/node_modules/lighthouse/core/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

Current File : /usr/lib/node_modules/lighthouse/core/runner.js
/**
 * @license
 * Copyright 2016 Google LLC
 * SPDX-License-Identifier: Apache-2.0
 */

import fs from 'fs';
import path from 'path';


import log from 'lighthouse-logger';
import {isEqual} from 'lodash-es';

import {ReportScoring} from './scoring.js';
import {Audit} from './audits/audit.js';
import * as format from '../shared/localization/format.js';
import * as stackPacks from './lib/stack-packs.js';
import * as assetSaver from './lib/asset-saver.js';
import {Sentry} from './lib/sentry.js';
import {ReportGenerator} from '../report/generator/report-generator.js';
import {LighthouseError} from './lib/lh-error.js';
import {lighthouseVersion} from '../shared/root.js';
import {getModuleDirectory} from '../shared/esm-utils.js';
import {EntityClassification} from './computed/entity-classification.js';
import UrlUtils from './lib/url-utils.js';

const moduleDir = getModuleDirectory(import.meta);

/** @typedef {import('./lib/arbitrary-equality-map.js').ArbitraryEqualityMap} ArbitraryEqualityMap */

class Runner {
  /**
   * @param {LH.Artifacts} artifacts
   * @param {{resolvedConfig: LH.Config.ResolvedConfig, computedCache: Map<string, ArbitraryEqualityMap>}} options
   * @return {Promise<LH.RunnerResult|undefined>}
   */
  static async audit(artifacts, options) {
    const {resolvedConfig, computedCache} = options;
    const settings = resolvedConfig.settings;

    try {
      const runnerStatus = {msg: 'Audit phase', id: 'lh:runner:audit'};
      log.time(runnerStatus, 'verbose');

      /**
       * List of top-level warnings for this Lighthouse run.
       * @type {Array<string | LH.IcuMessage>}
       */
      const lighthouseRunWarnings = [];

      // Potentially quit early
      if (settings.gatherMode && !settings.auditMode) return;

      if (!resolvedConfig.audits) {
        throw new Error('No audits to evaluate.');
      }
      const auditResultsById = await Runner._runAudits(settings, resolvedConfig.audits, artifacts,
          lighthouseRunWarnings, computedCache);

      // LHR construction phase
      const resultsStatus = {msg: 'Generating results...', id: 'lh:runner:generate'};
      log.time(resultsStatus);

      if (artifacts.LighthouseRunWarnings) {
        lighthouseRunWarnings.push(...artifacts.LighthouseRunWarnings);
      }

      // Entering: conclusion of the lighthouse result object

      // Use version from gathering stage.
      // If accessibility gatherer didn't run or errored, it won't be in credits.
      const axeVersion = artifacts.Accessibility?.version;
      const credits = {
        'axe-core': axeVersion,
      };

      /** @type {Record<string, LH.RawIcu<LH.Result.Category>>} */
      let categories = {};
      if (resolvedConfig.categories) {
        categories = ReportScoring.scoreAllCategories(resolvedConfig.categories, auditResultsById);
      }

      log.timeEnd(resultsStatus);
      log.timeEnd(runnerStatus);

      /** @type {LH.Artifacts['FullPageScreenshot']|undefined} */
      let fullPageScreenshot = artifacts.FullPageScreenshot;
      if (resolvedConfig.settings.disableFullPageScreenshot ||
          fullPageScreenshot instanceof Error) {
        fullPageScreenshot = undefined;
      }

      /** @type {LH.RawIcu<LH.Result>} */
      const i18nLhr = {
        lighthouseVersion,
        requestedUrl: artifacts.URL.requestedUrl,
        mainDocumentUrl: artifacts.URL.mainDocumentUrl,
        finalDisplayedUrl: artifacts.URL.finalDisplayedUrl,
        finalUrl: artifacts.URL.mainDocumentUrl,
        fetchTime: artifacts.fetchTime,
        gatherMode: artifacts.GatherContext.gatherMode,
        runtimeError: Runner.getArtifactRuntimeError(artifacts),
        runWarnings: lighthouseRunWarnings,
        userAgent: artifacts.HostUserAgent,
        environment: {
          networkUserAgent: artifacts.NetworkUserAgent,
          hostUserAgent: artifacts.HostUserAgent,
          benchmarkIndex: artifacts.BenchmarkIndex,
          credits,
        },
        audits: auditResultsById,
        configSettings: settings,
        categories,
        categoryGroups: resolvedConfig.groups || undefined,
        stackPacks: stackPacks.getStackPacks(artifacts.Stacks),
        entities: await Runner.getEntityClassification(artifacts, {computedCache}),
        fullPageScreenshot,
        timing: this._getTiming(artifacts),
        i18n: {
          rendererFormattedStrings: format.getRendererFormattedStrings(settings.locale),
          icuMessagePaths: {},
        },
      };

      // Replace ICU message references with localized strings; save replaced paths in lhr.
      i18nLhr.i18n.icuMessagePaths = format.replaceIcuMessages(i18nLhr, settings.locale);

      // LHR has now been localized.
      const lhr = /** @type {LH.Result} */ (i18nLhr);

      if (settings.auditMode) {
        const path = Runner._getDataSavePath(settings);
        assetSaver.saveLhr(lhr, path);
      }

      // Create the HTML, JSON, and/or CSV string
      const report = ReportGenerator.generateReport(lhr, settings.output);

      return {lhr, artifacts, report};
    } catch (err) {
      throw Runner.createRunnerError(err, settings);
    }
  }

  /**
   * @param {LH.Artifacts} artifacts
   * @param {LH.Artifacts.ComputedContext} context
   */
  static async getEntityClassification(artifacts, context) {
    const devtoolsLog = artifacts.DevtoolsLog;
    if (!devtoolsLog) return;

    const classifiedEntities = await EntityClassification.request(
      {URL: artifacts.URL, devtoolsLog}, context);

    /** @type {Array<LH.Result.LhrEntity>} */
    const entities = [];
    for (const [entity, entityUrls] of classifiedEntities.urlsByEntity) {
      const uniqueOrigins = new Set();
      for (const url of entityUrls) {
        const origin = UrlUtils.getOrigin(url);
        if (origin) uniqueOrigins.add(origin);
      }

      /** @type {LH.Result.LhrEntity} */
      const shortEntity = {
        name: entity.name,
        homepage: entity.homepage,
        origins: [...uniqueOrigins],
      };
      // Reduce payload size in LHR JSON by omitting whats falsy.
      if (entity === classifiedEntities.firstParty) shortEntity.isFirstParty = true;
      if (entity.isUnrecognized) shortEntity.isUnrecognized = true;
      if (entity.category) shortEntity.category = entity.category;
      entities.push(shortEntity);
    }

    return entities;
  }

  /**
   * User can run -G solo, -A solo, or -GA together
   * -G and -A will run partial lighthouse pipelines,
   * and -GA will run everything plus save artifacts and lhr to disk.
   *
   * @param {(runnerData: {resolvedConfig: LH.Config.ResolvedConfig}) => Promise<LH.Artifacts>} gatherFn
   * @param {{resolvedConfig: LH.Config.ResolvedConfig, computedCache: Map<string, ArbitraryEqualityMap>}} options
   * @return {Promise<LH.Artifacts>}
   */
  static async gather(gatherFn, options) {
    const settings = options.resolvedConfig.settings;

    // Either load saved artifacts from disk or from the browser.
    try {
      const sentryContext = Sentry.getContext();
      Sentry.captureBreadcrumb({
        message: 'Run started',
        category: 'lifecycle',
        data: sentryContext,
      });

      if (settings.auditMode && !settings.gatherMode) {
        // No browser required, just load the artifacts from disk.
        const path = this._getDataSavePath(settings);
        return assetSaver.loadArtifacts(path);
      }

      const runnerStatus = {msg: 'Gather phase', id: 'lh:runner:gather'};
      log.time(runnerStatus, 'verbose');

      const artifacts = await gatherFn({resolvedConfig: options.resolvedConfig});
      log.timeEnd(runnerStatus);

      // If `gather` is run multiple times before `audit`, the timing entries for each `gather` can pollute one another.
      // We need to clear the timing entries at the end of gathering.
      // Set artifacts.Timing again to ensure lh:runner:gather is included.
      artifacts.Timing = log.takeTimeEntries();

      // -G means save these to disk (e.g. ./latest-run).
      if (settings.gatherMode) {
        const path = this._getDataSavePath(settings);
        await assetSaver.saveArtifacts(artifacts, path);
      }

      return artifacts;
    } catch (err) {
      throw Runner.createRunnerError(err, settings);
    }
  }

  /**
   * @param {any} err
   * @param {LH.Config.Settings} settings
   */
  static createRunnerError(err, settings) {
    // i18n LighthouseError strings.
    if (err.friendlyMessage) {
      err.friendlyMessage = format.getFormatted(err.friendlyMessage, settings.locale);
    }
    Sentry.captureException(err, {level: 'fatal'});
    return err;
  }

  /**
   * This handles both the auditMode case where gatherer entries need to be merged in and
   * the gather/audit case where timingEntriesFromRunner contains all entries from this run,
   * including those also in timingEntriesFromArtifacts.
   * @param {LH.Artifacts} artifacts
   * @return {LH.Result.Timing}
   */
  static _getTiming(artifacts) {
    const timingEntriesFromArtifacts = artifacts.Timing || [];
    const timingEntriesFromRunner = log.takeTimeEntries();
    const timingEntriesKeyValues = [
      ...timingEntriesFromArtifacts,
      ...timingEntriesFromRunner,
    ].map(entry => /** @type {[string, PerformanceEntry]} */ ([
      // As entries can share a name and start time, dedupe based on the name, startTime and duration
      `${entry.startTime}-${entry.name}-${entry.duration}`,
      entry,
    ]));
    const timingEntries = Array.from(new Map(timingEntriesKeyValues).values())
    // Truncate timestamps to hundredths of a millisecond saves ~4KB. No need for microsecond
    // resolution.
    .map(entry => {
      return {
        // Don't spread entry because browser PerformanceEntries can't be spread.
        // https://github.com/GoogleChrome/lighthouse/issues/8638
        startTime: parseFloat(entry.startTime.toFixed(2)),
        name: entry.name,
        duration: parseFloat(entry.duration.toFixed(2)),
        entryType: entry.entryType,
      };
    }).sort((a, b) => a.startTime - b.startTime);
    const gatherEntry = timingEntries.find(e => e.name === 'lh:runner:gather');
    const auditEntry = timingEntries.find(e => e.name === 'lh:runner:audit');
    const gatherTiming = gatherEntry?.duration || 0;
    const auditTiming = auditEntry?.duration || 0;
    return {entries: timingEntries, total: gatherTiming + auditTiming};
  }

  /**
   * Run all audits with specified settings and artifacts.
   * @param {LH.Config.Settings} settings
   * @param {Array<LH.Config.AuditDefn>} audits
   * @param {LH.Artifacts} artifacts
   * @param {Array<string | LH.IcuMessage>} runWarnings
   * @param {Map<string, ArbitraryEqualityMap>} computedCache
   * @return {Promise<Record<string, LH.RawIcu<LH.Audit.Result>>>}
   */
  static async _runAudits(settings, audits, artifacts, runWarnings, computedCache) {
    const status = {msg: 'Analyzing and running audits...', id: 'lh:runner:auditing'};
    log.time(status);

    if (artifacts.settings) {
      const overrides = {
        locale: undefined,
        gatherMode: undefined,
        auditMode: undefined,
        output: undefined,
        channel: undefined,
      };
      const normalizedGatherSettings = Object.assign({}, artifacts.settings, overrides);
      const normalizedAuditSettings = Object.assign({}, settings, overrides);

      // First, try each key individually so we can print which key differed.
      const keys = new Set([
        ...Object.keys(normalizedGatherSettings),
        ...Object.keys(normalizedAuditSettings),
      ]);
      for (const k of keys) {
        if (!isEqual(normalizedGatherSettings[k], normalizedAuditSettings[k])) {
          throw new Error(
            `Cannot change settings between gathering and auditing…
Difference found at: \`${k}\`: ${JSON.stringify(normalizedGatherSettings[k], null, 2)}
vs: ${JSON.stringify(normalizedAuditSettings[k], null, 2)}`);
        }
      }

      // Call `isEqual` on the entire thing, just in case something was missed.
      if (!isEqual(normalizedGatherSettings, normalizedAuditSettings)) {
        throw new Error('Cannot change settings between gathering and auditing');
      }
    }

    // Members of LH.Audit.Context that are shared across all audits.
    const sharedAuditContext = {
      settings,
      computedCache,
    };

    // Run each audit sequentially
    /** @type {Record<string, LH.RawIcu<LH.Audit.Result>>} */
    const auditResultsById = {};
    for (const auditDefn of audits) {
      const auditId = auditDefn.implementation.meta.id;
      const auditResult = await Runner._runAudit(auditDefn, artifacts, sharedAuditContext,
          runWarnings);
      auditResultsById[auditId] = auditResult;
    }

    log.timeEnd(status);
    return auditResultsById;
  }

  /**
   * Checks that the audit's required artifacts exist and runs the audit if so.
   * Otherwise returns error audit result.
   * @param {LH.Config.AuditDefn} auditDefn
   * @param {LH.Artifacts} artifacts
   * @param {Pick<LH.Audit.Context, 'settings'|'computedCache'>} sharedAuditContext
   * @param {Array<string | LH.IcuMessage>} runWarnings
   * @return {Promise<LH.RawIcu<LH.Audit.Result>>}
   * @private
   */
  static async _runAudit(auditDefn, artifacts, sharedAuditContext, runWarnings) {
    const audit = auditDefn.implementation;
    const status = {
      msg: `Auditing: ${format.getFormatted(audit.meta.title, 'en-US')}`,
      id: `lh:audit:${audit.meta.id}`,
    };
    log.time(status);

    let auditResult;
    try {
      if (artifacts.PageLoadError) throw artifacts.PageLoadError;

      // Return an early error if an artifact required for the audit is missing or an error.
      for (const artifactName of audit.meta.requiredArtifacts) {
        const noArtifact = artifacts[artifactName] === undefined;

        if (noArtifact) {
          log.warn('Runner',
              `${artifactName} gatherer, required by audit ${audit.meta.id}, did not run.`);
          throw new LighthouseError(
            LighthouseError.errors.MISSING_REQUIRED_ARTIFACT, {artifactName});
        }

        // If artifact was an error, output error result on behalf of audit.
        if (artifacts[artifactName] instanceof Error) {
          /** @type {Error} */
          const artifactError = artifacts[artifactName];

          log.warn('Runner', `${artifactName} gatherer, required by audit ${audit.meta.id},` +
            ` encountered an error: ${artifactError.message}`);

          // Create a friendlier display error and mark it as expected to avoid duplicates in Sentry.
          // The artifact error was already sent to Sentry in `collectPhaseArtifacts`.
          const error = new LighthouseError(LighthouseError.errors.ERRORED_REQUIRED_ARTIFACT,
              {artifactName, errorMessage: artifactError.message}, {cause: artifactError});
          // @ts-expect-error Non-standard property added to Error
          error.expected = true;
          throw error;
        }
      }

      // all required artifacts are in good shape, so we proceed
      const auditOptions = Object.assign({}, audit.defaultOptions, auditDefn.options);
      const auditContext = {
        options: auditOptions,
        ...sharedAuditContext,
      };

      // Only pass the declared required and optional artifacts to the audit
      // The type is masquerading as `LH.Artifacts` but will only contain a subset of the keys
      // to prevent consumers from unnecessary type assertions.
      const requestedArtifacts = audit.meta.requiredArtifacts
        .concat(audit.meta.__internalOptionalArtifacts || []);
      const narrowedArtifacts = requestedArtifacts
        .reduce((narrowedArtifacts, artifactName) => {
          const requestedArtifact = artifacts[artifactName];
          // @ts-expect-error tsc can't yet express that artifactName is only a single type in each iteration, not a union of types.
          narrowedArtifacts[artifactName] = requestedArtifact;
          return narrowedArtifacts;
        }, /** @type {LH.Artifacts} */ ({}));
      const product = await audit.audit(narrowedArtifacts, auditContext);
      runWarnings.push(...(product.runWarnings || []));

      auditResult = Audit.generateAuditResult(audit, product);
    } catch (err) {
      // Log error if it hasn't already been logged above.
      if (err.code !== 'MISSING_REQUIRED_ARTIFACT' && err.code !== 'ERRORED_REQUIRED_ARTIFACT') {
        log.warn(audit.meta.id, `Caught exception: ${err.message}`);
      }

      Sentry.captureException(err, {tags: {audit: audit.meta.id}, level: 'error'});
      // Errors become error audit result.
      const errorMessage = err.friendlyMessage ? err.friendlyMessage : err.message;
      // Prefer the stack trace closest to the error.
      const stack = err.cause?.stack ?? err.stack;
      auditResult = Audit.generateErrorAuditResult(audit, errorMessage, stack);
    }

    log.timeEnd(status);
    return auditResult;
  }

  /**
   * Searches a pass's artifacts for any `lhrRuntimeError` error artifacts.
   * Returns the first one found or `null` if none found.
   * @param {LH.Artifacts} artifacts
   * @return {LH.RawIcu<LH.Result['runtimeError']>|undefined}
   */
  static getArtifactRuntimeError(artifacts) {
    /** @type {Array<[string, LighthouseError|object]>} */
    const possibleErrorArtifacts = [
      ['PageLoadError', artifacts.PageLoadError], // Preferentially use `PageLoadError`, if it exists.
      ...Object.entries(artifacts), // Otherwise check amongst all artifacts.
    ];

    for (const [artifactKey, possibleErrorArtifact] of possibleErrorArtifacts) {
      const isError = possibleErrorArtifact instanceof LighthouseError;

      if (isError && possibleErrorArtifact.lhrRuntimeError) {
        const errorMessage = possibleErrorArtifact.friendlyMessage || possibleErrorArtifact.message;
        // Prefer the stack trace closest to the error.
        const stack =
          /** @type {any} */ (possibleErrorArtifact.cause)?.stack ?? possibleErrorArtifact.stack;

        return {
          code: possibleErrorArtifact.code,
          message: errorMessage,
          errorStack: stack,
          artifactKey,
        };
      }
    }

    return undefined;
  }

  /**
   * Returns list of audit names for external querying.
   * @return {Array<string>}
   */
  static getAuditList() {
    const ignoredFiles = [
      'audit.js',
      'violation-audit.js',
      'accessibility/axe-audit.js',
      'multi-check-audit.js',
      'byte-efficiency/byte-efficiency-audit.js',
      'manual/manual-audit.js',
      'insights/insight-audit.js',
    ];

    const fileList = [
      ...fs.readdirSync(path.join(moduleDir, './audits')),
      ...fs.readdirSync(path.join(moduleDir, './audits/dobetterweb')).map(f => `dobetterweb/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/metrics')).map(f => `metrics/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/seo')).map(f => `seo/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/seo/manual')).map(f => `seo/manual/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/accessibility'))
          .map(f => `accessibility/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/accessibility/manual'))
          .map(f => `accessibility/manual/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/byte-efficiency'))
          .map(f => `byte-efficiency/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/manual')).map(f => `manual/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/insights')).map(f => `insights/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './audits/agentic')).map(f => `agentic/${f}`),
    ];
    return fileList.filter(f => {
      return /\.js$/.test(f) && !ignoredFiles.includes(f);
    }).sort();
  }

  /**
   * Returns list of gatherer names for external querying.
   * @return {Array<string>}
   */
  static getGathererList() {
    const fileList = [
      ...fs.readdirSync(path.join(moduleDir, './gather/gatherers')),
      ...fs.readdirSync(path.join(moduleDir, './gather/gatherers/seo')).map(f => `seo/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './gather/gatherers/dobetterweb'))
          .map(f => `dobetterweb/${f}`),
      ...fs.readdirSync(path.join(moduleDir, './gather/gatherers/agentic'))
          .map(f => `agentic/${f}`),
    ];
    return fileList.filter(f => /\.js$/.test(f) && f !== 'gatherer.js').sort();
  }

  /**
   * Get path to use for -G and -A modes. Defaults to $CWD/latest-run
   * @param {LH.Config.Settings} settings
   * @return {string}
   */
  static _getDataSavePath(settings) {
    const {auditMode, gatherMode} = settings;

    // This enables usage like: -GA=./custom-folder
    if (typeof auditMode === 'string') return path.resolve(process.cwd(), auditMode);
    if (typeof gatherMode === 'string') return path.resolve(process.cwd(), gatherMode);

    return path.join(process.cwd(), 'latest-run');
  }
}

export {Runner};

Youez - 2016 - github.com/yon3zu
LinuXploit