@flakiness/sdk
Advanced tools
| export type TelemetryPoint = { | ||
| timestamp: number; | ||
| value: number; | ||
| }; | ||
| export declare function addTelemetryPoint(collection: TelemetryPoint[], newPoint: TelemetryPoint, precision: number): void; | ||
| export declare function toProtocolTelemetry(collection: TelemetryPoint[]): [number, number][]; | ||
| //# sourceMappingURL=_telemetry.d.ts.map |
| {"version":3,"file":"_telemetry.d.ts","sourceRoot":"","sources":["../../src/_telemetry.ts"],"names":[],"mappings":"AACA,MAAM,MAAM,cAAc,GAAG;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,KAAK,EAAE,MAAM,CAAC;CACf,CAAC;AAEF,wBAAgB,iBAAiB,CAAC,UAAU,EAAE,cAAc,EAAE,EAAE,QAAQ,EAAE,cAAc,EAAE,SAAS,EAAE,MAAM,QAY1G;AAED,wBAAgB,mBAAmB,CAAC,UAAU,EAAE,cAAc,EAAE,GAAG,CAAC,MAAM,EAAE,MAAM,CAAC,EAAE,CASpF"} |
| import { FlakinessReport as FK } from '@flakiness/flakiness-report'; | ||
| import { GitWorktree } from './gitWorktree.js'; | ||
| /** | ||
| * Extracts source code snippets referenced by locations in the report. | ||
| * | ||
| * Scans all locations in the report (tests, steps, errors, annotations) and collects | ||
| * the relevant source code chunks with surrounding context (±5 lines). The collected | ||
| * sources are stored directly in `report.sources`. | ||
| * | ||
| * @param worktree - Git worktree for resolving file paths. | ||
| * @param report - Report to scan and enrich with source snippets. | ||
| */ | ||
| export declare function collectSources(worktree: GitWorktree, report: FK.Report): void; | ||
| //# sourceMappingURL=collectSources.d.ts.map |
| {"version":3,"file":"collectSources.d.ts","sourceRoot":"","sources":["../../src/collectSources.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAEpE,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAqD/C;;;;;;;;;GASG;AACH,wBAAgB,cAAc,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,EAAE,CAAC,MAAM,QAiCtE"} |
| import { FlakinessReport as FK } from "@flakiness/flakiness-report"; | ||
| /** | ||
| * Tracks CPU utilization over time by recording periodic samples. | ||
| * | ||
| * Call `sample()` at desired intervals (e.g., test start/end, every second) to record | ||
| * CPU usage. The class tracks both average and max utilization across all CPU cores. | ||
| * Use `enrich()` to add the collected telemetry to a report. | ||
| */ | ||
| export declare class CPUUtilization { | ||
| private _lastSample; | ||
| private _precision; | ||
| private _cpuAvg; | ||
| private _cpuMax; | ||
| /** | ||
| * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7. | ||
| */ | ||
| constructor(options?: { | ||
| precision?: number; | ||
| }); | ||
| /** | ||
| * Records the current CPU utilization since the last sample. | ||
| * The first call primes the baseline; subsequent calls record deltas. | ||
| */ | ||
| sample(): void; | ||
| /** | ||
| * Adds collected CPU telemetry to the report. | ||
| */ | ||
| enrich(report: FK.Report): void; | ||
| } | ||
| //# sourceMappingURL=cpuUtilization.d.ts.map |
| {"version":3,"file":"cpuUtilization.d.ts","sourceRoot":"","sources":["../../src/cpuUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AAkBpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,WAAW,CAAgB;IAEnC,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,OAAO,CAAwB;IACvC,OAAO,CAAC,OAAO,CAAwB;IAEvC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;;OAGG;IACH,MAAM;IAwBN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAKzB"} |
| import { FlakinessReport as FK } from '@flakiness/flakiness-report'; | ||
| /** | ||
| * Tracks RAM utilization over time by recording periodic samples. | ||
| * | ||
| * Call `sample()` at desired intervals (e.g., test start/end, every second) to record | ||
| * memory usage as a percentage of total system RAM. Use `enrich()` to add the collected | ||
| * telemetry to a report. | ||
| */ | ||
| export declare class RAMUtilization { | ||
| private _precision; | ||
| private _totalBytes; | ||
| private _ram; | ||
| /** | ||
| * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1. | ||
| */ | ||
| constructor(options?: { | ||
| precision?: number; | ||
| }); | ||
| /** | ||
| * Records the current RAM utilization. | ||
| */ | ||
| sample(): void; | ||
| /** | ||
| * Adds collected RAM telemetry to the report. | ||
| */ | ||
| enrich(report: FK.Report): void; | ||
| } | ||
| //# sourceMappingURL=ramUtilization.d.ts.map |
| {"version":3,"file":"ramUtilization.d.ts","sourceRoot":"","sources":["../../src/ramUtilization.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,EAAE,EAAE,MAAM,6BAA6B,CAAC;AA4BpE;;;;;;GAMG;AACH,qBAAa,cAAc;IACzB,OAAO,CAAC,UAAU,CAAS;IAC3B,OAAO,CAAC,WAAW,CAAiB;IAEpC,OAAO,CAAC,IAAI,CAAwB;IAEpC;;OAEG;gBACS,OAAO,CAAC,EAAE;QACpB,SAAS,CAAC,EAAE,MAAM,CAAC;KACpB;IAID;;OAEG;IACH,MAAM;IAQN;;OAEG;IACH,MAAM,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM;CAIzB"} |
+52
-5
@@ -32,2 +32,43 @@ var __defProp = Object.defineProperty; | ||
| function normalizeReport(report) { | ||
| report = deduplicateTestsSuitesEnvironments(report); | ||
| function cleanupTestStep(step) { | ||
| return { | ||
| ...step, | ||
| duration: step.duration === 0 ? void 0 : step.duration, | ||
| steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0 | ||
| }; | ||
| } | ||
| function cleanupAttempt(attempt) { | ||
| return { | ||
| ...attempt, | ||
| status: attempt.status === "passed" ? void 0 : attempt.status, | ||
| expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus, | ||
| environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx, | ||
| duration: attempt.duration === 0 ? void 0 : attempt.duration, | ||
| stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0, | ||
| stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0, | ||
| attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0, | ||
| steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0 | ||
| }; | ||
| } | ||
| function cleanupTest(test) { | ||
| return { | ||
| ...test, | ||
| attempts: test.attempts.map(cleanupAttempt) | ||
| }; | ||
| } | ||
| function cleanupSuite(suite) { | ||
| return { | ||
| ...suite, | ||
| tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0, | ||
| suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0 | ||
| }; | ||
| } | ||
| return { | ||
| ...report, | ||
| tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0, | ||
| suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0 | ||
| }; | ||
| } | ||
| function deduplicateTestsSuitesEnvironments(report) { | ||
| const gEnvs = /* @__PURE__ */ new Map(); | ||
@@ -54,5 +95,11 @@ const gSuites = /* @__PURE__ */ new Map(); | ||
| for (const attempt of test.attempts) { | ||
| const env = report.environments[attempt.environmentIdx]; | ||
| const env = report.environments[attempt.environmentIdx ?? 0]; | ||
| const envId = gEnvIds.get(env); | ||
| usedEnvIds.add(envId); | ||
| if (attempt.annotations && !attempt.annotations.length) | ||
| delete attempt.annotations; | ||
| if (attempt.stdout && !attempt.stdout.length) | ||
| delete attempt.stdout; | ||
| if (attempt.stderr && !attempt.stderr.length) | ||
| delete attempt.stderr; | ||
| } | ||
@@ -82,3 +129,3 @@ } | ||
| ...attempt, | ||
| environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx])) | ||
| environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0])) | ||
| })) | ||
@@ -102,3 +149,3 @@ }; | ||
| visitTests2(report.tests ?? [], "suiteless"); | ||
| for (const suite of report.suites) | ||
| for (const suite of report.suites ?? []) | ||
| visitSuite(suite); | ||
@@ -110,3 +157,3 @@ const newEnvironments = [...usedEnvIds]; | ||
| environments: newEnvironments.map((envId) => gEnvs.get(envId)), | ||
| suites: transformSuites(report.suites), | ||
| suites: transformSuites(report.suites ?? []), | ||
| tests: transformTests(report.tests ?? []) | ||
@@ -152,3 +199,3 @@ }; | ||
| testVisitor(test, []); | ||
| for (const suite of report.suites) | ||
| for (const suite of report.suites ?? []) | ||
| visitSuite(suite, []); | ||
@@ -155,0 +202,0 @@ } |
+285
-162
@@ -49,2 +49,82 @@ var __defProp = Object.defineProperty; | ||
| // src/cpuUtilization.ts | ||
| import os from "os"; | ||
| // src/_telemetry.ts | ||
| function addTelemetryPoint(collection, newPoint, precision) { | ||
| const lastPoint = collection.at(-1); | ||
| const preLastPoint = collection.at(-2); | ||
| if (lastPoint && preLastPoint && Math.abs(lastPoint.value - preLastPoint.value) < precision && Math.abs(lastPoint.value - newPoint.value) < precision) { | ||
| lastPoint.timestamp = newPoint.timestamp; | ||
| } else { | ||
| collection.push(newPoint); | ||
| } | ||
| } | ||
| function toProtocolTelemetry(collection) { | ||
| if (!collection.length) | ||
| return []; | ||
| let lastTimestamp = collection[0].timestamp; | ||
| return collection.map((x, idx) => { | ||
| const dts = idx === 0 ? x.timestamp : x.timestamp - lastTimestamp; | ||
| lastTimestamp = x.timestamp; | ||
| return [dts, Math.round(x.value * 100) / 100]; | ||
| }); | ||
| } | ||
| // src/cpuUtilization.ts | ||
| function sampleCpus() { | ||
| return os.cpus().map((cpu) => { | ||
| const totalTicks = cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle; | ||
| const idleTicks = cpu.times.idle; | ||
| const busyTicks = totalTicks - idleTicks; | ||
| return { totalTicks, busyTicks }; | ||
| }); | ||
| } | ||
| var CPUUtilization = class { | ||
| _lastSample = sampleCpus(); | ||
| _precision; | ||
| _cpuAvg = []; | ||
| _cpuMax = []; | ||
| /** | ||
| * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 7. | ||
| */ | ||
| constructor(options) { | ||
| this._precision = options?.precision ?? 7; | ||
| } | ||
| /** | ||
| * Records the current CPU utilization since the last sample. | ||
| * The first call primes the baseline; subsequent calls record deltas. | ||
| */ | ||
| sample() { | ||
| const newSample = sampleCpus(); | ||
| if (newSample.length === this._lastSample.length) { | ||
| const utilization = newSample.map( | ||
| (cpu, idx) => ( | ||
| // If the CPU did no work since the last sample, then it's | ||
| // utilization is effectively 0. | ||
| cpu.totalTicks === this._lastSample[idx].totalTicks ? 0 : (cpu.busyTicks - this._lastSample[idx].busyTicks) / (cpu.totalTicks - this._lastSample[idx].totalTicks) * 100 | ||
| ) | ||
| ); | ||
| const timestamp = Date.now(); | ||
| addTelemetryPoint(this._cpuAvg, { | ||
| timestamp, | ||
| value: utilization.reduce((acc, x) => acc + x) / utilization.length | ||
| }, this._precision); | ||
| addTelemetryPoint(this._cpuMax, { | ||
| timestamp, | ||
| value: Math.max(...utilization) | ||
| }, this._precision); | ||
| } | ||
| this._lastSample = newSample; | ||
| } | ||
| /** | ||
| * Adds collected CPU telemetry to the report. | ||
| */ | ||
| enrich(report) { | ||
| report.cpuCount = os.cpus().length; | ||
| report.cpuMax = toProtocolTelemetry(this._cpuMax); | ||
| report.cpuAvg = toProtocolTelemetry(this._cpuAvg); | ||
| } | ||
| }; | ||
| // src/gitWorktree.ts | ||
@@ -339,9 +419,58 @@ import assert from "assert"; | ||
| // src/ramUtilization.ts | ||
| import { spawnSync as spawnSync2 } from "child_process"; | ||
| import os2 from "os"; | ||
| function getAvailableMemMacOS() { | ||
| const lines = spawnSync2("vm_stat", { encoding: "utf8" }).stdout.trim().split("\n"); | ||
| const pageSize = parseInt(lines[0].match(/page size of (\d+) bytes/)[1], 10); | ||
| if (isNaN(pageSize)) { | ||
| console.warn("[flakiness.io] Error detecting macos page size"); | ||
| return 0; | ||
| } | ||
| let totalFree = 0; | ||
| for (const line of lines) { | ||
| if (/Pages (free|inactive|speculative):/.test(line)) { | ||
| const match = line.match(/\d+/); | ||
| if (match) | ||
| totalFree += parseInt(match[0], 10); | ||
| } | ||
| } | ||
| return totalFree * pageSize; | ||
| } | ||
| var RAMUtilization = class { | ||
| _precision; | ||
| _totalBytes = os2.totalmem(); | ||
| _ram = []; | ||
| /** | ||
| * @param options.precision - Minimum change in percentage points to record a new data point. Defaults to 1. | ||
| */ | ||
| constructor(options) { | ||
| this._precision = options?.precision ?? 1; | ||
| } | ||
| /** | ||
| * Records the current RAM utilization. | ||
| */ | ||
| sample() { | ||
| const freeBytes = os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem(); | ||
| addTelemetryPoint(this._ram, { | ||
| timestamp: Date.now(), | ||
| value: (this._totalBytes - freeBytes) / this._totalBytes * 100 | ||
| }, this._precision); | ||
| } | ||
| /** | ||
| * Adds collected RAM telemetry to the report. | ||
| */ | ||
| enrich(report) { | ||
| report.ramBytes = this._totalBytes; | ||
| report.ram = toProtocolTelemetry(this._ram); | ||
| } | ||
| }; | ||
| // src/reportUtils.ts | ||
| var reportUtils_exports = {}; | ||
| __export(reportUtils_exports, { | ||
| collectSources: () => collectSources, | ||
| createDataAttachment: () => createDataAttachment, | ||
| createEnvironment: () => createEnvironment, | ||
| createFileAttachment: () => createFileAttachment, | ||
| createTestStepSnippetsInplace: () => createTestStepSnippetsInplace, | ||
| normalizeReport: () => normalizeReport, | ||
@@ -352,7 +481,89 @@ stripAnsi: () => stripAnsi, | ||
| // src/collectSources.ts | ||
| import fs2 from "fs"; | ||
| function collectLocationsFromTestStep(testStep, onLocation) { | ||
| onLocation(testStep.location); | ||
| for (const step of testStep.steps ?? []) | ||
| collectLocationsFromTestStep(step, onLocation); | ||
| } | ||
| function collectLocationsFromTest(test, onLocation) { | ||
| onLocation(test.location); | ||
| for (const attempt of test.attempts) { | ||
| for (const annotation of attempt.annotations ?? []) | ||
| onLocation(annotation.location); | ||
| for (const err of attempt.errors ?? []) | ||
| onLocation(err.location); | ||
| for (const step of attempt.steps ?? []) | ||
| collectLocationsFromTestStep(step, onLocation); | ||
| } | ||
| } | ||
| function collectLocationsFromSuite(suite, onLocation) { | ||
| onLocation(suite.location); | ||
| for (const child of suite.suites ?? []) | ||
| collectLocationsFromSuite(child, onLocation); | ||
| for (const test of suite.tests ?? []) | ||
| collectLocationsFromTest(test, onLocation); | ||
| } | ||
| function collectLocationsFromReport(report, onLocation) { | ||
| for (const e of report.unattributedErrors ?? []) | ||
| onLocation(e.location); | ||
| for (const test of report.tests ?? []) | ||
| collectLocationsFromTest(test, onLocation); | ||
| for (const suite of report.suites ?? []) | ||
| collectLocationsFromSuite(suite, onLocation); | ||
| } | ||
| function lineNumbersToChunks(lineNumbers, options) { | ||
| const context = options.context; | ||
| const result = []; | ||
| let current; | ||
| for (const ln of Array.from(lineNumbers).sort((a, b) => a - b)) { | ||
| const span = [ln - context, ln + context]; | ||
| if (!current || current[1] + 1 < span[0]) { | ||
| result.push(span); | ||
| current = span; | ||
| } else { | ||
| current[1] = span[1]; | ||
| } | ||
| } | ||
| return result; | ||
| } | ||
| function collectSources(worktree, report) { | ||
| const filesToLines = /* @__PURE__ */ new Map(); | ||
| collectLocationsFromReport(report, (location) => { | ||
| if (!location) | ||
| return; | ||
| let lineNumbers = filesToLines.get(location.file); | ||
| if (!lineNumbers) { | ||
| lineNumbers = /* @__PURE__ */ new Set(); | ||
| filesToLines.set(location.file, lineNumbers); | ||
| } | ||
| lineNumbers.add(location.line); | ||
| }); | ||
| const sources = []; | ||
| for (const [gitFilePath, lineNumbers] of filesToLines) { | ||
| let source; | ||
| try { | ||
| source = fs2.readFileSync(worktree.absolutePath(gitFilePath), "utf-8"); | ||
| } catch (e) { | ||
| continue; | ||
| } | ||
| const sourceLines = source.split("\n"); | ||
| for (const chunk of lineNumbersToChunks(lineNumbers, { context: 5 })) { | ||
| const from = Math.max(chunk[0] - 1, 0); | ||
| const to = Math.min(chunk[1], sourceLines.length); | ||
| sources.push({ | ||
| filePath: gitFilePath, | ||
| lineOffset: from !== 0 ? from + 1 : void 0, | ||
| text: sourceLines.slice(from, to).join("\n") | ||
| }); | ||
| } | ||
| } | ||
| report.sources = sources; | ||
| } | ||
| // src/createEnvironment.ts | ||
| import fs2 from "fs"; | ||
| import os from "os"; | ||
| import fs3 from "fs"; | ||
| import os3 from "os"; | ||
| function readLinuxOSRelease() { | ||
| const osReleaseText = fs2.readFileSync("/etc/os-release", "utf-8"); | ||
| const osReleaseText = fs3.readFileSync("/etc/os-release", "utf-8"); | ||
| return new Map(osReleaseText.toLowerCase().split("\n").filter((line) => line.includes("=")).map((line) => { | ||
@@ -382,3 +593,3 @@ line = line.trim(); | ||
| const arch = process.arch; | ||
| const version = os.release(); | ||
| const version = os3.release(); | ||
| return { name, arch, version }; | ||
@@ -408,72 +619,9 @@ } | ||
| }, | ||
| userSuppliedData: { | ||
| metadata: { | ||
| ...extractEnvConfiguration(), | ||
| ...options.userSuppliedData ?? {} | ||
| }, | ||
| opaqueData: options.opaqueData | ||
| ...options.metadata ?? {} | ||
| } | ||
| }; | ||
| } | ||
| // src/createTestStepSnippets.ts | ||
| import { codeFrameColumns } from "@babel/code-frame"; | ||
| import fs3 from "fs"; | ||
| // src/visitTests.ts | ||
| function visitTests(report, testVisitor) { | ||
| function visitSuite(suite, parents) { | ||
| parents.push(suite); | ||
| for (const test of suite.tests ?? []) | ||
| testVisitor(test, parents); | ||
| for (const childSuite of suite.suites ?? []) | ||
| visitSuite(childSuite, parents); | ||
| parents.pop(); | ||
| } | ||
| for (const test of report.tests ?? []) | ||
| testVisitor(test, []); | ||
| for (const suite of report.suites) | ||
| visitSuite(suite, []); | ||
| } | ||
| // src/createTestStepSnippets.ts | ||
| function createTestStepSnippetsInplace(worktree, report) { | ||
| const allSteps = /* @__PURE__ */ new Map(); | ||
| visitTests(report, (test) => { | ||
| for (const attempt of test.attempts) { | ||
| for (const step of attempt.steps ?? []) { | ||
| if (!step.location) | ||
| continue; | ||
| let fileSteps = allSteps.get(step.location.file); | ||
| if (!fileSteps) { | ||
| fileSteps = /* @__PURE__ */ new Set(); | ||
| allSteps.set(step.location.file, fileSteps); | ||
| } | ||
| fileSteps.add(step); | ||
| } | ||
| } | ||
| }); | ||
| for (const [gitFilePath, steps] of allSteps) { | ||
| let source; | ||
| try { | ||
| source = fs3.readFileSync(worktree.absolutePath(gitFilePath), "utf-8"); | ||
| } catch (e) { | ||
| continue; | ||
| } | ||
| const lines = source.split("\n").length; | ||
| const highlighted = codeFrameColumns(source, { start: { line: lines, column: 1 } }, { highlightCode: true, linesAbove: lines, linesBelow: 0 }); | ||
| const highlightedLines = highlighted.split("\n"); | ||
| const lineWithArrow = highlightedLines[highlightedLines.length - 1]; | ||
| for (const step of steps) { | ||
| if (!step.location) | ||
| continue; | ||
| if (step.location.line < 2 || step.location.line >= lines) | ||
| continue; | ||
| const snippetLines = highlightedLines.slice(step.location.line - 2, step.location.line + 1); | ||
| const index = lineWithArrow.indexOf("^"); | ||
| const shiftedArrow = lineWithArrow.slice(0, index) + " ".repeat(step.location.column - 1) + lineWithArrow.slice(index); | ||
| snippetLines.splice(2, 0, shiftedArrow); | ||
| step.snippet = snippetLines.join("\n"); | ||
| } | ||
| } | ||
| } | ||
| // src/normalizeReport.ts | ||
@@ -493,2 +641,43 @@ import stableObjectHash from "stable-hash"; | ||
| function normalizeReport(report) { | ||
| report = deduplicateTestsSuitesEnvironments(report); | ||
| function cleanupTestStep(step) { | ||
| return { | ||
| ...step, | ||
| duration: step.duration === 0 ? void 0 : step.duration, | ||
| steps: step.steps && step.steps.length ? step.steps.map(cleanupTestStep) : void 0 | ||
| }; | ||
| } | ||
| function cleanupAttempt(attempt) { | ||
| return { | ||
| ...attempt, | ||
| status: attempt.status === "passed" ? void 0 : attempt.status, | ||
| expectedStatus: attempt.expectedStatus === "passed" ? void 0 : attempt.expectedStatus, | ||
| environmentIdx: attempt.environmentIdx === 0 ? void 0 : attempt.environmentIdx, | ||
| duration: attempt.duration === 0 ? void 0 : attempt.duration, | ||
| stdout: attempt.stdout && attempt.stdout.length ? attempt.stdout : void 0, | ||
| stderr: attempt.stderr && attempt.stderr.length ? attempt.stderr : void 0, | ||
| attachments: attempt.attachments && attempt.attachments.length ? attempt.attachments : void 0, | ||
| steps: attempt.steps && attempt.steps.length ? attempt.steps.map(cleanupTestStep) : void 0 | ||
| }; | ||
| } | ||
| function cleanupTest(test) { | ||
| return { | ||
| ...test, | ||
| attempts: test.attempts.map(cleanupAttempt) | ||
| }; | ||
| } | ||
| function cleanupSuite(suite) { | ||
| return { | ||
| ...suite, | ||
| tests: suite.tests && suite.tests.length ? suite.tests.map(cleanupTest) : void 0, | ||
| suites: suite.suites && suite.suites.length ? suite.suites.map(cleanupSuite) : void 0 | ||
| }; | ||
| } | ||
| return { | ||
| ...report, | ||
| tests: report.tests && report.tests.length ? report.tests.map(cleanupTest) : void 0, | ||
| suites: report.suites && report.suites.length ? report.suites.map(cleanupSuite) : void 0 | ||
| }; | ||
| } | ||
| function deduplicateTestsSuitesEnvironments(report) { | ||
| const gEnvs = /* @__PURE__ */ new Map(); | ||
@@ -515,5 +704,11 @@ const gSuites = /* @__PURE__ */ new Map(); | ||
| for (const attempt of test.attempts) { | ||
| const env = report.environments[attempt.environmentIdx]; | ||
| const env = report.environments[attempt.environmentIdx ?? 0]; | ||
| const envId = gEnvIds.get(env); | ||
| usedEnvIds.add(envId); | ||
| if (attempt.annotations && !attempt.annotations.length) | ||
| delete attempt.annotations; | ||
| if (attempt.stdout && !attempt.stdout.length) | ||
| delete attempt.stdout; | ||
| if (attempt.stderr && !attempt.stderr.length) | ||
| delete attempt.stderr; | ||
| } | ||
@@ -543,3 +738,3 @@ } | ||
| ...attempt, | ||
| environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx])) | ||
| environmentIdx: envIdToIndex.get(gEnvIds.get(report.environments[attempt.environmentIdx ?? 0])) | ||
| })) | ||
@@ -563,3 +758,3 @@ }; | ||
| visitTests2(report.tests ?? [], "suiteless"); | ||
| for (const suite of report.suites) | ||
| for (const suite of report.suites ?? []) | ||
| visitSuite(suite); | ||
@@ -571,3 +766,3 @@ const newEnvironments = [...usedEnvIds]; | ||
| environments: newEnvironments.map((envId) => gEnvs.get(envId)), | ||
| suites: transformSuites(report.suites), | ||
| suites: transformSuites(report.suites ?? []), | ||
| tests: transformTests(report.tests ?? []) | ||
@@ -771,90 +966,17 @@ }; | ||
| // src/systemUtilizationSampler.ts | ||
| import { spawnSync as spawnSync2 } from "child_process"; | ||
| import os2 from "os"; | ||
| function getAvailableMemMacOS() { | ||
| const lines = spawnSync2("vm_stat", { encoding: "utf8" }).stdout.trim().split("\n"); | ||
| const pageSize = parseInt(lines[0].match(/page size of (\d+) bytes/)[1], 10); | ||
| if (isNaN(pageSize)) { | ||
| console.warn("[flakiness.io] Error detecting macos page size"); | ||
| return 0; | ||
| // src/visitTests.ts | ||
| function visitTests(report, testVisitor) { | ||
| function visitSuite(suite, parents) { | ||
| parents.push(suite); | ||
| for (const test of suite.tests ?? []) | ||
| testVisitor(test, parents); | ||
| for (const childSuite of suite.suites ?? []) | ||
| visitSuite(childSuite, parents); | ||
| parents.pop(); | ||
| } | ||
| let totalFree = 0; | ||
| for (const line of lines) { | ||
| if (/Pages (free|inactive|speculative):/.test(line)) { | ||
| const match = line.match(/\d+/); | ||
| if (match) | ||
| totalFree += parseInt(match[0], 10); | ||
| } | ||
| } | ||
| return totalFree * pageSize; | ||
| for (const test of report.tests ?? []) | ||
| testVisitor(test, []); | ||
| for (const suite of report.suites ?? []) | ||
| visitSuite(suite, []); | ||
| } | ||
| function getSystemUtilization() { | ||
| let idleTicks = 0; | ||
| let totalTicks = 0; | ||
| for (const cpu of os2.cpus()) { | ||
| totalTicks += cpu.times.user + cpu.times.nice + cpu.times.sys + cpu.times.irq + cpu.times.idle; | ||
| idleTicks += cpu.times.idle; | ||
| } | ||
| return { | ||
| idleTicks, | ||
| totalTicks, | ||
| timestamp: Date.now(), | ||
| freeBytes: os2.platform() === "darwin" ? getAvailableMemMacOS() : os2.freemem() | ||
| }; | ||
| } | ||
| function toFKUtilization(sample, previous) { | ||
| const idleTicks = sample.idleTicks - previous.idleTicks; | ||
| const totalTicks = sample.totalTicks - previous.totalTicks; | ||
| const cpuUtilization = Math.floor((1 - idleTicks / totalTicks) * 1e4) / 100; | ||
| const memoryUtilization = Math.floor((1 - sample.freeBytes / os2.totalmem()) * 1e4) / 100; | ||
| return { | ||
| cpuUtilization, | ||
| memoryUtilization, | ||
| dts: sample.timestamp - previous.timestamp | ||
| }; | ||
| } | ||
| var SystemUtilizationSampler = class { | ||
| /** | ||
| * The accumulated system utilization data. | ||
| * | ||
| * This object is populated as samples are collected and can be directly included in | ||
| * Flakiness reports. It contains: | ||
| * - `samples` - Array of utilization samples with CPU/memory percentages and durations | ||
| * - `startTimestamp` - Timestamp when sampling began | ||
| * - `totalMemoryBytes` - Total system memory in bytes | ||
| */ | ||
| result; | ||
| _lastSample = getSystemUtilization(); | ||
| _timer; | ||
| /** | ||
| * Creates a new SystemUtilizationSampler and starts sampling immediately. | ||
| * | ||
| * The first sample is collected after 50ms, and subsequent samples are collected | ||
| * every 1000ms. Call `dispose()` to stop sampling and clean up resources. | ||
| */ | ||
| constructor() { | ||
| this.result = { | ||
| samples: [], | ||
| startTimestamp: this._lastSample.timestamp, | ||
| totalMemoryBytes: os2.totalmem() | ||
| }; | ||
| this._timer = setTimeout(this._addSample.bind(this), 50); | ||
| } | ||
| _addSample() { | ||
| const sample = getSystemUtilization(); | ||
| this.result.samples.push(toFKUtilization(sample, this._lastSample)); | ||
| this._lastSample = sample; | ||
| this._timer = setTimeout(this._addSample.bind(this), 1e3); | ||
| } | ||
| /** | ||
| * Stops sampling and cleans up resources. | ||
| * | ||
| * Call this method when you're done collecting utilization data to stop the sampling | ||
| * timer and prevent memory leaks. The `result` object remains accessible after disposal. | ||
| */ | ||
| dispose() { | ||
| clearTimeout(this._timer); | ||
| } | ||
| }; | ||
@@ -1201,7 +1323,8 @@ // src/showReport.ts | ||
| CIUtils, | ||
| CPUUtilization, | ||
| FlakinessProjectConfig, | ||
| FlakinessReport, | ||
| GitWorktree, | ||
| RAMUtilization, | ||
| reportUtils_exports as ReportUtils, | ||
| SystemUtilizationSampler, | ||
| showReport, | ||
@@ -1208,0 +1331,0 @@ uploadReport, |
+3
-5
| { | ||
| "name": "@flakiness/sdk", | ||
| "version": "0.151.0", | ||
| "version": "0.152.0", | ||
| "private": false, | ||
@@ -22,3 +22,3 @@ "repository": { | ||
| "type": "module", | ||
| "description": "", | ||
| "description": "Comprehensive SDK for creating and managing Flakiness JSON Reports in Node.js", | ||
| "types": "./types/index.d.ts", | ||
@@ -33,3 +33,2 @@ "scripts": { | ||
| "devDependencies": { | ||
| "@types/babel__code-frame": "^7.0.6", | ||
| "@types/debug": "^4.1.12", | ||
@@ -43,4 +42,3 @@ "@types/node": "^25.0.3", | ||
| "dependencies": { | ||
| "@babel/code-frame": "^7.26.2", | ||
| "@flakiness/flakiness-report": "^0.16.0", | ||
| "@flakiness/flakiness-report": "^0.18.0", | ||
| "chalk": "^5.6.2", | ||
@@ -47,0 +45,0 @@ "debug": "^4.4.3", |
@@ -12,6 +12,4 @@ import { FlakinessReport } from '@flakiness/flakiness-report'; | ||
| * @param {string} options.name - Human-readable name for the environment (e.g., 'CI', 'Local Dev', 'Staging'). | ||
| * @param {Record<string, string>} [options.userSuppliedData] - Additional key-value pairs to include | ||
| * @param {Record<string, string>} [options.metadata] - Additional key-value pairs to include | ||
| * in the environment data. These are merged with `FK_ENV_*` environment variables. | ||
| * @param {any} [options.opaqueData] - Optional opaque data object that will be stored with the | ||
| * environment but not used for environment deduplication. | ||
| * | ||
@@ -21,4 +19,3 @@ * @returns {FlakinessReport.Environment} Environment object containing: | ||
| * - `systemData` - Automatically detected OS information (arch, name, version) | ||
| * - `userSuppliedData` - Merged data from `FK_ENV_*` variables and `userSuppliedData` option | ||
| * - `opaqueData` - The provided opaque data, if any | ||
| * - `metadata` - Merged data from `FK_ENV_*` variables and `userSuppliedData` option | ||
| * | ||
@@ -33,3 +30,3 @@ * @example | ||
| * name: 'Staging', | ||
| * userSuppliedData: { region: 'us-east-1', instance: 'large' } | ||
| * metadata: { region: 'us-east-1', instance: 'large' } | ||
| * }); | ||
@@ -40,5 +37,4 @@ * ``` | ||
| name: string; | ||
| userSuppliedData?: Record<string, string>; | ||
| opaqueData?: any; | ||
| metadata?: Record<string, string>; | ||
| }): FlakinessReport.Environment; | ||
| //# sourceMappingURL=createEnvironment.d.ts.map |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,gBAAgB,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;IAC1C,UAAU,CAAC,EAAE,GAAG,CAAC;CAClB,GAAG,eAAe,CAAC,WAAW,CAe9B"} | ||
| {"version":3,"file":"createEnvironment.d.ts","sourceRoot":"","sources":["../../src/createEnvironment.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAwD9D;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,CAAC;CACnC,GAAG,eAAe,CAAC,WAAW,CAc9B"} |
| export { FlakinessReport } from '@flakiness/flakiness-report'; | ||
| export { CIUtils } from './ciUtils.js'; | ||
| export { CPUUtilization } from './cpuUtilization.js'; | ||
| export { GitWorktree } from './gitWorktree.js'; | ||
| export { RAMUtilization } from './ramUtilization.js'; | ||
| export * as ReportUtils from './reportUtils.js'; | ||
| export { SystemUtilizationSampler } from './systemUtilizationSampler.js'; | ||
| export { showReport } from './showReport.js'; | ||
@@ -7,0 +8,0 @@ export { uploadReport } from './uploadReport.js'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAChD,OAAO,EAAE,wBAAwB,EAAE,MAAM,+BAA+B,CAAC;AAGzE,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"} | ||
| {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/index.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAG9D,OAAO,EAAE,OAAO,EAAE,MAAM,cAAc,CAAC;AACvC,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAC/C,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,KAAK,WAAW,MAAM,kBAAkB,CAAC;AAGhD,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC;AAC7C,OAAO,EAAE,YAAY,EAAE,MAAM,mBAAmB,CAAC;AACjD,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C,OAAO,EAAE,sBAAsB,EAAE,MAAM,6BAA6B,CAAC"} |
| import { FlakinessReport } from "@flakiness/flakiness-report"; | ||
| /** | ||
| * Normalizes a Flakiness report by deduplicating environments, suites, and tests. | ||
| * It also drops the fields from JSON that are equal to their default values. | ||
| * | ||
@@ -5,0 +6,0 @@ * This function processes a report to: |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D;;;;;;;;;;;;;;;;;;;;;;GAsBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,CA2FtF"} | ||
| {"version":3,"file":"normalizeReport.d.ts","sourceRoot":"","sources":["../../src/normalizeReport.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAyB9D;;;;;;;;;;;;;;;;;;;;;;;GAuBG;AACH,wBAAgB,eAAe,CAAC,MAAM,EAAE,eAAe,CAAC,MAAM,GAAG,eAAe,CAAC,MAAM,CA+CtF"} |
@@ -0,3 +1,3 @@ | ||
| export { collectSources } from './collectSources.js'; | ||
| export { createEnvironment } from './createEnvironment.js'; | ||
| export { createTestStepSnippetsInplace } from './createTestStepSnippets.js'; | ||
| export { normalizeReport } from './normalizeReport.js'; | ||
@@ -4,0 +4,0 @@ export { stripAnsi } from './stripAnsi.js'; |
@@ -1,1 +0,1 @@ | ||
| {"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,6BAA6B,EAAE,MAAM,6BAA6B,CAAC;AAC5E,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"} | ||
| {"version":3,"file":"reportUtils.d.ts","sourceRoot":"","sources":["../../src/reportUtils.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AACrD,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAC3D,OAAO,EAAE,eAAe,EAAE,MAAM,sBAAsB,CAAC;AACvD,OAAO,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC3C,OAAO,EACL,oBAAoB,EACpB,oBAAoB,EACrB,MAAM,mBAAmB,CAAC;AAC3B,YAAY,EACV,UAAU,EAAE,cAAc,EAC1B,cAAc,EACf,MAAM,mBAAmB,CAAC;AAC3B,OAAO,EAAE,UAAU,EAAE,MAAM,iBAAiB,CAAC"} |
| import { FlakinessReport } from '@flakiness/flakiness-report'; | ||
| import { GitWorktree } from './gitWorktree.js'; | ||
| /** | ||
| * Generates code snippets for test steps and attaches them to the report in-place. | ||
| * | ||
| * This function reads source files from the git worktree and creates highlighted code snippets | ||
| * for each test step that has a location. The snippets include 3 lines of context (1 before, | ||
| * the line itself, 1 after) with syntax highlighting and a visual indicator pointing to the | ||
| * exact column position. | ||
| * | ||
| * The snippets are attached directly to the `step.snippet` property of each test step in the | ||
| * report object. Steps without locations or with invalid file paths are silently skipped. | ||
| * | ||
| * @param {GitWorktree} worktree - Git worktree instance used to resolve file paths from | ||
| * git-relative paths to absolute paths for reading source files. | ||
| * | ||
| * @param {FlakinessReport.Report} report - Flakiness report to process. The report is modified | ||
| * in-place by adding `snippet` properties to test steps. | ||
| * | ||
| * @returns {void} This function modifies the report in-place and does not return a value. | ||
| * | ||
| * @example | ||
| * ```typescript | ||
| * const worktree = GitWorktree.create(process.cwd()); | ||
| * createTestStepSnippetsInplace(worktree, report); | ||
| * // Report steps now have .snippet properties with highlighted code | ||
| * ``` | ||
| */ | ||
| export declare function createTestStepSnippetsInplace(worktree: GitWorktree, report: FlakinessReport.Report): void; | ||
| //# sourceMappingURL=createTestStepSnippets.d.ts.map |
| {"version":3,"file":"createTestStepSnippets.d.ts","sourceRoot":"","sources":["../../src/createTestStepSnippets.ts"],"names":[],"mappings":"AAiBA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AAE9D,OAAO,EAAE,WAAW,EAAE,MAAM,kBAAkB,CAAC;AAG/C;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,6BAA6B,CAAC,QAAQ,EAAE,WAAW,EAAE,MAAM,EAAE,eAAe,CAAC,MAAM,QA4ClG"} |
| import { FlakinessReport } from "@flakiness/flakiness-report"; | ||
| /** | ||
| * Samples and records system CPU and memory utilization over time. | ||
| * | ||
| * This class continuously monitors system resource usage at regular intervals and stores | ||
| * the samples in a format suitable for inclusion in Flakiness reports. Sampling starts | ||
| * immediately upon construction and continues until `dispose()` is called. | ||
| * | ||
| * The first sample is collected after 50ms, and subsequent samples are collected every | ||
| * 1000ms (1 second). CPU utilization is calculated as a percentage based on CPU tick | ||
| * differences between samples. Memory utilization uses platform-specific methods for | ||
| * accurate measurement (especially on macOS). | ||
| */ | ||
| export declare class SystemUtilizationSampler { | ||
| /** | ||
| * The accumulated system utilization data. | ||
| * | ||
| * This object is populated as samples are collected and can be directly included in | ||
| * Flakiness reports. It contains: | ||
| * - `samples` - Array of utilization samples with CPU/memory percentages and durations | ||
| * - `startTimestamp` - Timestamp when sampling began | ||
| * - `totalMemoryBytes` - Total system memory in bytes | ||
| */ | ||
| readonly result: FlakinessReport.SystemUtilization; | ||
| private _lastSample; | ||
| private _timer; | ||
| /** | ||
| * Creates a new SystemUtilizationSampler and starts sampling immediately. | ||
| * | ||
| * The first sample is collected after 50ms, and subsequent samples are collected | ||
| * every 1000ms. Call `dispose()` to stop sampling and clean up resources. | ||
| */ | ||
| constructor(); | ||
| private _addSample; | ||
| /** | ||
| * Stops sampling and cleans up resources. | ||
| * | ||
| * Call this method when you're done collecting utilization data to stop the sampling | ||
| * timer and prevent memory leaks. The `result` object remains accessible after disposal. | ||
| */ | ||
| dispose(): void; | ||
| } | ||
| //# sourceMappingURL=systemUtilizationSampler.d.ts.map |
| {"version":3,"file":"systemUtilizationSampler.d.ts","sourceRoot":"","sources":["../../src/systemUtilizationSampler.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,EAAE,MAAM,6BAA6B,CAAC;AA6D9D;;;;;;;;;;;GAWG;AACH,qBAAa,wBAAwB;IACnC;;;;;;;;OAQG;IACH,SAAgB,MAAM,EAAE,eAAe,CAAC,iBAAiB,CAAC;IAE1D,OAAO,CAAC,WAAW,CAA0B;IAC7C,OAAO,CAAC,MAAM,CAAiB;IAE/B;;;;;OAKG;;IAWH,OAAO,CAAC,UAAU;IAOlB;;;;;OAKG;IACH,OAAO;CAGR"} |
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 14 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
Network access
Supply chain riskThis module accesses the network.
Found 2 instances in 1 package
Shell access
Supply chain riskThis module accesses the system shell. Accessing the system shell increases the risk of executing arbitrary code.
Found 1 instance in 1 package
Environment variable access
Supply chain riskPackage accesses environment variables, which may be a sign of credential stuffing or data theft.
Found 14 instances in 1 package
Filesystem access
Supply chain riskAccesses the file system, and could potentially read sensitive data.
Found 1 instance in 1 package
Long strings
Supply chain riskContains long string literals, which may be a sign of obfuscated or packed code.
Found 1 instance in 1 package
URL strings
Supply chain riskPackage contains fragments of external URLs or IP addresses, which the package may be accessing at runtime.
Found 1 instance in 1 package
99248
6.6%5
-16.67%6
-14.29%45
9.76%2255
8.15%26
8.33%+ Added
- Removed
- Removed
- Removed
- Removed
- Removed
- Removed