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/lib/

Upload File :
current_dir [ Writeable ] document_root [ Writeable ]

 

Command :


[ Back ]     

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

import fs from 'fs';
import path from 'path';
import stream from 'stream';
import url from 'url';
import {createGzip, gunzipSync} from 'zlib';

import log from 'lighthouse-logger';

import * as Lantern from './lantern/lantern.js';
import lanternTraceSaver from './lantern-trace-saver.js';
import {MetricTraceEvents} from './traces/metric-trace-events.js';
import {NetworkAnalysis} from '../computed/network-analysis.js';
import {LoadSimulator} from '../computed/load-simulator.js';
import {LighthouseError} from '../lib/lh-error.js';
import {LH_ROOT} from '../../shared/root.js';

const optionsFilename = 'options.json';
const artifactsFilename = 'artifacts.json';
const traceFilename = 'trace.json';
const devtoolsFilename = 'devtoolslog.json';
const errorPrefix = 'pageLoadError.';
const stepDirectoryRegex = /^step(\d+)$/;

/**
 * @typedef {object} PreparedAssets
 * @property {LH.Trace} [traceData]
 * @property {LH.DevtoolsLog} [devtoolsLog]
 */

/**
 * @param {import('stream').PipelineSource<any>} contents
 * @param {string} path
 * @param {boolean} gzip
 */
async function writeJson(contents, path, gzip) {
  const writeStream = fs.createWriteStream(gzip ? path + '.gz' : path);
  if (gzip) {
    await stream.promises.pipeline(contents, createGzip(), writeStream);
  } else {
    await stream.promises.pipeline(contents, writeStream);
  }
}

/**
 * Prefers reading a gzipped file (.gz) if present.
 * @param {string} filename
 * @param {(this: any, key: string, value: any) => any=} reviver
 */
function readJson(filename, reviver) {
  if (fs.existsSync(filename + '.gz')) {
    filename = filename + '.gz';
  }

  if (!filename.endsWith('.json.gz')) {
    return JSON.parse(fs.readFileSync(filename, 'utf8'), reviver);
  }

  const buffer = gunzipSync(fs.readFileSync(filename));
  return JSON.parse(buffer.toString('utf8'), reviver);
}

/**
 * @param {string} filename
 * @param {string} suffix
 * @returns
 */
function endsWithSuffix(filename, suffix) {
  return filename.endsWith(suffix) || filename.endsWith(suffix + '.gz');
}

/**
 * Load artifacts object from files located within basePath
 * Also save the traces to their own files
 * @param {string} basePath
 * @return {LH.Artifacts}
 */
function loadArtifacts(basePath) {
  log.log('Reading artifacts from disk:', basePath);

  if (!fs.existsSync(basePath)) {
    throw new Error('No saved artifacts found at ' + basePath);
  }

  // load artifacts.json using a reviver to deserialize any LighthouseErrors in artifacts.
  /** @type {LH.Artifacts} */
  const artifacts = readJson(path.join(basePath, artifactsFilename), LighthouseError.parseReviver);

  const filenames = fs.readdirSync(basePath);

  filenames.filter(f => endsWithSuffix(f, devtoolsFilename)).forEach(filename => {
    const devtoolsLog = readJson(path.join(basePath, filename));

    if (filename.startsWith(devtoolsFilename)) {
      artifacts.DevtoolsLog = devtoolsLog;
    } else if (filename.startsWith(errorPrefix)) {
      artifacts.DevtoolsLogError = devtoolsLog;
    }
  });

  filenames.filter(f => endsWithSuffix(f, traceFilename)).forEach(filename => {
    let trace = readJson(path.join(basePath, filename));
    if (Array.isArray(trace)) {
      trace = {traceEvents: trace};
    }

    if (filename.startsWith(traceFilename)) {
      artifacts.Trace = trace;
    } else if (filename.startsWith(errorPrefix)) {
      artifacts.TraceError = trace;
    }
  });

  if (Array.isArray(artifacts.Timing)) {
    // Any Timing entries in saved artifacts will have a different timeOrigin than the auditing phase
    // The `gather` prop is read later in generate-timing-trace and they're added to a separate track of trace events
    artifacts.Timing.forEach(entry => (entry.gather = true));
  }
  return artifacts;
}

/**
 * @param {string} basePath
 * @return {LH.UserFlow.FlowArtifacts}
 */
function loadFlowArtifacts(basePath) {
  log.log('Reading flow artifacts from disk:', basePath);

  if (!fs.existsSync(basePath)) {
    throw new Error('No saved flow artifacts found at ' + basePath);
  }

  /** @type {LH.UserFlow.FlowArtifacts} */
  const flowArtifacts = JSON.parse(
    fs.readFileSync(path.join(basePath, optionsFilename), 'utf-8')
  );

  const filenames = fs.readdirSync(basePath);

  flowArtifacts.gatherSteps = [];
  for (const filename of filenames) {
    const regexResult = stepDirectoryRegex.exec(filename);
    if (!regexResult) continue;

    const index = Number(regexResult[1]);
    if (!Number.isFinite(index)) continue;

    const stepPath = path.join(basePath, filename);
    if (!fs.lstatSync(stepPath).isDirectory()) continue;

    /** @type {LH.UserFlow.GatherStep} */
    const gatherStep = JSON.parse(
      fs.readFileSync(path.join(stepPath, optionsFilename), 'utf-8')
    );
    gatherStep.artifacts = loadArtifacts(stepPath);

    flowArtifacts.gatherSteps[index] = gatherStep;
  }

  const missingStepIndex = flowArtifacts.gatherSteps.findIndex(gatherStep => !gatherStep);
  if (missingStepIndex !== -1) {
    throw new Error(`Could not find step with index ${missingStepIndex} at ${basePath}`);
  }

  return flowArtifacts;
}

/**
 * A replacer function for JSON.stingify of the artifacts. Used to serialize objects that
 * JSON won't normally handle.
 * @param {string} key
 * @param {any} value
 */
function stringifyReplacer(key, value) {
  // Currently only handle LighthouseError and other Error types.
  if (value instanceof Error) {
    return LighthouseError.stringifyReplacer(value);
  }

  return value;
}

/**
 * Saves flow artifacts with the following file structure:
 *   flow/                             --  Directory specified by `basePath`.
 *     options.json                    --  Flow options (e.g. flow name, flags).
 *     step0/                          --  Directory containing artifacts for the first step.
 *       options.json                  --  First step's options (e.g. step flags).
 *       artifacts.json                --  First step's artifacts except the DevTools log and trace.
 *       devtoolslog.json              --  First step's DevTools log.
 *       trace.json                    --  First step's trace.
 *     step1/                          --  Directory containing artifacts for the second step.
 *
 * @param {LH.UserFlow.FlowArtifacts} flowArtifacts
 * @param {string} basePath
 * @return {Promise<void>}
 */
async function saveFlowArtifacts(flowArtifacts, basePath) {
  const status = {msg: 'Saving flow artifacts', id: 'lh:assetSaver:saveArtifacts'};
  log.time(status);
  fs.mkdirSync(basePath, {recursive: true});

  // Delete any previous artifacts in this directory.
  const filenames = fs.readdirSync(basePath);
  for (const filename of filenames) {
    if (stepDirectoryRegex.test(filename) || filename === optionsFilename) {
      fs.rmSync(`${basePath}/${filename}`, {recursive: true});
    }
  }

  const {gatherSteps, ...flowOptions} = flowArtifacts;
  for (let i = 0; i < gatherSteps.length; ++i) {
    const {artifacts, ...stepOptions} = gatherSteps[i];
    const stepPath = path.join(basePath, `step${i}`);
    await saveArtifacts(artifacts, stepPath);
    fs.writeFileSync(
      path.join(stepPath, optionsFilename),
      JSON.stringify(stepOptions, stringifyReplacer, 2) + '\n'
    );
  }

  fs.writeFileSync(
    path.join(basePath, optionsFilename),
    JSON.stringify(flowOptions, stringifyReplacer, 2) + '\n'
  );

  log.log('Flow artifacts saved to disk in folder:', basePath);
  log.timeEnd(status);
}

/**
 * Save artifacts object mostly to single file located at basePath/artifacts.json.
 * Also save the traces & devtoolsLogs to their own files, with optional compression.
 * @param {LH.Artifacts} artifacts
 * @param {string} basePath
 * @param {{gzip?: boolean}} options
 * @return {Promise<void>}
 */
async function saveArtifacts(artifacts, basePath, options = {}) {
  const status = {msg: 'Saving artifacts', id: 'lh:assetSaver:saveArtifacts'};
  log.time(status);
  fs.mkdirSync(basePath, {recursive: true});

  // Delete any previous artifacts in this directory.
  const filenames = fs.readdirSync(basePath);
  for (const filename of filenames) {
    const isPreviousFile =
      filename.endsWith(traceFilename) || filename.endsWith(devtoolsFilename) ||
      filename.endsWith(traceFilename + '.gz') || filename.endsWith(devtoolsFilename + '.gz') ||
      filename === artifactsFilename || filename === artifactsFilename + '.gz';
    if (isPreviousFile) {
      fs.unlinkSync(`${basePath}/${filename}`);
    }
  }

  const {
    DevtoolsLog,
    Trace,
    DevtoolsLogError,
    TraceError,
    ...restArtifacts
  } = artifacts;

  if (Trace) {
    await saveTrace(Trace, `${basePath}/${traceFilename}`, options);
  }

  if (TraceError) {
    await saveTrace(TraceError, `${basePath}/${errorPrefix}${traceFilename}`, options);
  }

  if (DevtoolsLog) {
    await saveDevtoolsLog(
      DevtoolsLog, `${basePath}/${devtoolsFilename}`, options);
  }

  if (DevtoolsLogError) {
    await saveDevtoolsLog(
      DevtoolsLogError, `${basePath}/${errorPrefix}${devtoolsFilename}`, options);
  }

  // save everything else, using a replacer to serialize LighthouseErrors in the artifacts.
  const restArtifactsString = JSON.stringify(restArtifacts, stringifyReplacer, 2);
  await writeJson(function* () {
    yield restArtifactsString;
    yield '\n';
  }, `${basePath}/${artifactsFilename}`, !!options.gzip);

  log.log('Artifacts saved to disk in folder:', basePath);
  log.timeEnd(status);
}

/**
 * Save LHR to file located at basePath/lhr.report.json.
 * @param {LH.Result} lhr
 * @param {string} basePath
 */
function saveLhr(lhr, basePath) {
  fs.writeFileSync(`${basePath}/lhr.report.json`, JSON.stringify(lhr, null, 2));
}

/**
 * Filter trace and extract screenshots to prepare for saving.
 * @param {LH.Trace} trace
 * @param {LH.Result['audits']} [audits]
 * @return {LH.Trace}
 */
function prepareTraceAsset(trace, audits) {
  if (!trace) return trace;

  const traceData = Object.assign({}, trace);
  if (audits) {
    const evts = new MetricTraceEvents(traceData.traceEvents, audits).generateFakeEvents();
    traceData.traceEvents = traceData.traceEvents.concat(evts);
  }
  return traceData;
}

/**
 * @param {LH.Artifacts} artifacts
 * @param {LH.Result['audits']} [audits]
 * @return {Promise<Array<PreparedAssets>>}
 */
async function prepareAssets(artifacts, audits) {
  /** @type {Array<PreparedAssets>} */
  const assets = [];

  const devtoolsLog = artifacts.DevtoolsLog;
  const traceData = prepareTraceAsset(artifacts.Trace, audits);
  if (traceData || devtoolsLog) {
    assets.push({
      traceData,
      devtoolsLog,
    });
  }

  const devtoolsLogError = artifacts.DevtoolsLogError;
  const traceErrorData = prepareTraceAsset(artifacts.TraceError, audits);
  if (devtoolsLogError || traceErrorData) {
    assets.push({
      traceData: traceErrorData,
      devtoolsLog: devtoolsLogError,
    });
  }

  return assets;
}

/**
 * Generates a JSON representation of an array of objects with the objects
 * printed one per line for a more readable (but not too verbose) version.
 * @param {Array<unknown>} arrayOfObjects
 * @return {IterableIterator<string>}
 */
function* arrayOfObjectsJsonGenerator(arrayOfObjects) {
  const ITEMS_PER_ITERATION = 500;

  // Stringify and emit items separately to avoid a giant string in memory.
  yield '[\n';
  if (arrayOfObjects.length > 0) {
    const itemsIterator = arrayOfObjects[Symbol.iterator]();
    // Emit first item manually to avoid a trailing comma.
    const firstItem = itemsIterator.next().value;
    yield `  ${JSON.stringify(firstItem)}`;

    let itemsRemaining = ITEMS_PER_ITERATION;
    let itemsJSON = '';
    for (const item of itemsIterator) {
      itemsJSON += `,\n  ${JSON.stringify(item)}`;
      itemsRemaining--;
      if (itemsRemaining === 0) {
        yield itemsJSON;
        itemsRemaining = ITEMS_PER_ITERATION;
        itemsJSON = '';
      }
    }
    yield itemsJSON;
  }
  yield '\n]';
}

/**
 * Generates a JSON representation of traceData line-by-line for a nicer printed
 * version with one trace event per line.
 * @param {LH.Trace} traceData
 * @return {IterableIterator<string>}
 */
function* traceJsonGenerator(traceData) {
  const {traceEvents, ...rest} = traceData;

  yield '{\n';

  yield '"traceEvents": ';
  yield* arrayOfObjectsJsonGenerator(traceEvents);

  // Emit the rest of the object (usually just `metadata`, if anything).
  for (const [key, value] of Object.entries(rest)) {
    yield `,\n"${key}": ${JSON.stringify(value, null, 2)}`;
  }

  yield '}\n';
}

/**
 * Save a trace as JSON by streaming to disk at traceFilename.
 * @param {LH.Trace} traceData
 * @param {string} traceFilename
 * @param {{gzip?: boolean}=} options
 * @return {Promise<void>}
 */
function saveTrace(traceData, traceFilename, options = {}) {
  const traceIter = traceJsonGenerator(traceData);
  return writeJson(traceIter, traceFilename, !!options.gzip);
}

/**
 * Save a devtoolsLog as JSON by streaming to disk at devtoolLogFilename.
 * @param {LH.DevtoolsLog} devtoolsLog
 * @param {string} devtoolLogFilename
 * @param {{gzip?: boolean}=} options
 * @return {Promise<void>}
 */
function saveDevtoolsLog(devtoolsLog, devtoolLogFilename, options = {}) {
  return writeJson(function* () {
    yield* arrayOfObjectsJsonGenerator(devtoolsLog);
    yield '\n';
  }, devtoolLogFilename, !!options.gzip);
}

/**
 * @param {string} pathWithBasename
 * @return {Promise<void>}
 */
async function saveLanternDebugTraces(pathWithBasename) {
  if (!process.env.LANTERN_DEBUG) return;

  for (const [label, nodeTimings] of Lantern.Simulation.Simulator.allNodeTimings) {
    if (lanternTraceSaver.simulationNamesToIgnore.includes(label)) continue;

    const filename = `${pathWithBasename}-${label}.${traceFilename}`;
    await saveTrace(lanternTraceSaver.convertNodeTimingsToTrace(nodeTimings), filename);
    log.log('saveAssets', `${label} lantern trace file streamed to disk: ${filename}`);
  }
}

/**
 * Writes trace(s) and associated asset(s) to disk.
 * @param {LH.Artifacts} artifacts
 * @param {LH.Result['audits']} audits
 * @param {string} pathWithBasename
 * @return {Promise<void>}
 */
async function saveAssets(artifacts, audits, pathWithBasename) {
  const allAssets = await prepareAssets(artifacts, audits);
  const saveAll = allAssets.map(async (assets, index) => {
    if (assets.devtoolsLog) {
      const filename = `${pathWithBasename}-${index}.${devtoolsFilename}`;
      await saveDevtoolsLog(assets.devtoolsLog, filename);
      log.log('saveAssets', 'devtools log saved to disk: ' + filename);
    }

    if (assets.traceData) {
      const filename = `${pathWithBasename}-${index}.${traceFilename}`;
      await saveTrace(assets.traceData, filename);
      log.log('saveAssets', 'trace file streamed to disk: ' + filename);
    }
  });

  await Promise.all(saveAll);
  await saveLanternDebugTraces(pathWithBasename);
}

/**
 * @param {LH.DevtoolsLog} devtoolsLog
 * @param {string} outputPath
 * @return {Promise<void>}
 */
async function saveLanternNetworkData(devtoolsLog, outputPath) {
  /** @type {LH.Audit.Context} */
  // @ts-expect-error - the full audit context isn't needed for analysis.
  const context = {computedCache: new Map()};
  const networkAnalysis = await NetworkAnalysis.request(devtoolsLog, context);
  const lanternData = LoadSimulator.convertAnalysisToSaveableLanternData(networkAnalysis);

  fs.writeFileSync(outputPath, JSON.stringify(lanternData));
}

/**
 * Normalize timing data so it doesn't change every update.
 * @param {LH.Result.MeasureEntry[]} timings
 */
function normalizeTimingEntries(timings) {
  let baseTime = 0;
  for (const timing of timings) {
    // @ts-expect-error: Value actually is writeable at this point.
    timing.startTime = baseTime++;
    // @ts-expect-error: Value actually is writeable at this point.
    timing.duration = 1;
  }
}

/**
 * @param {string} errorStack
 * @return {string}
 */
function elideErrorStack(errorStack) {
  const baseCallFrameUrl = url.pathToFileURL(LH_ROOT);
  return errorStack
    // Make paths relative to the repo root.
    .replaceAll(baseCallFrameUrl.pathname, '')
    // Remove line/col info.
    .replaceAll(/:\d+:\d+/g, '');
}

/**
 * @param {LH.Result} lhr
 */
function elideLhrErrorStacks(lhr) {
  for (const auditResult of Object.values(lhr.audits)) {
    if (auditResult.errorStack) {
      auditResult.errorStack = elideErrorStack(auditResult.errorStack);
    }
  }

  if (lhr.runtimeError?.errorStack) {
    lhr.runtimeError.errorStack = elideErrorStack(lhr.runtimeError.errorStack);
  }
}

export {
  saveArtifacts,
  saveFlowArtifacts,
  saveLhr,
  loadArtifacts,
  loadFlowArtifacts,
  saveAssets,
  prepareAssets,
  saveTrace,
  saveDevtoolsLog,
  saveLanternNetworkData,
  stringifyReplacer,
  normalizeTimingEntries,
  elideLhrErrorStacks,
};

Youez - 2016 - github.com/yon3zu
LinuXploit