@google-cloud/debug-agent
Advanced tools
Comparing version 5.2.4 to 5.2.5
@@ -295,6 +295,6 @@ import { GoogleAuthOptions } from '@google-cloud/common'; | ||
/** | ||
* Number of times the V8 pauses (could be breakpoint hits) before the | ||
* debugging session is reset. This is to release the memory usage held by V8 | ||
* engine for each breakpoint hit to prevent the memory leak. The default | ||
* value is specified in defaultConfig. | ||
* Number of times of the V8 breakpoint hits events before resetting the | ||
* breakpoints. This is to release the memory usage held by V8 engine for each | ||
* breakpoint hit to prevent the memory leak. The default value is specified | ||
* in defaultConfig. | ||
*/ | ||
@@ -301,0 +301,0 @@ resetV8DebuggerThreshold: number; |
@@ -276,3 +276,3 @@ "use strict"; | ||
try { | ||
mapper = await SourceMapper.create(findResults.mapFiles); | ||
mapper = await SourceMapper.create(findResults.mapFiles, that.logger); | ||
} | ||
@@ -279,0 +279,0 @@ catch (err3) { |
import * as sourceMap from 'source-map'; | ||
import { Logger } from '../config'; | ||
/** Represents one source map file. */ | ||
export interface MapInfoInput { | ||
outputFile: string; | ||
inputFile: string; | ||
mapFile: string; | ||
@@ -21,13 +23,7 @@ mapConsumer: sourceMap.SourceMapConsumer; | ||
export declare class SourceMapper { | ||
readonly logger: Logger; | ||
/** Maps each original source path to the corresponding source map info. */ | ||
infoMap: Map<string, MapInfoInput>; | ||
constructor(logger: Logger); | ||
/** | ||
* @param {Array.<string>} sourcemapPaths An array of paths to .map sourcemap | ||
* files that should be processed. The paths should be relative to the | ||
* current process's current working directory | ||
* @param {Logger} logger A logger that reports errors that occurred while | ||
* processing the given sourcemap files | ||
* @constructor | ||
*/ | ||
constructor(); | ||
/** | ||
* Used to get the information about the transpiled file from a given input | ||
@@ -54,5 +50,2 @@ * source file provided there isn't any ambiguity with associating the input | ||
/** | ||
* @param {string} inputPath The path to an input file that could possibly | ||
* be the input to a transpilation process. The path should be relative to | ||
* the process's current working directory | ||
* @param {number} The line number in the input file where the line number is | ||
@@ -62,2 +55,5 @@ * zero-based. | ||
* specified where the column number is zero-based. | ||
* @param {string} The entry of the source map info in the sourceMapper. Such | ||
* an entry is supposed to be got by the getMapInfoInput method. | ||
* | ||
* @return {Object} The object returned has a "file" attribute for the | ||
@@ -74,4 +70,13 @@ * path of the output file associated with the given input file (where the | ||
*/ | ||
getMapInfoOutput(inputPath: string, lineNumber: number, colNumber: number, entry: MapInfoInput): MapInfoOutput | null; | ||
getMapInfoOutput(lineNumber: number, colNumber: number, entry: MapInfoInput): MapInfoOutput | null; | ||
/** Prints the debugging information of the source mapper to the logger. */ | ||
debug(): void; | ||
} | ||
export declare function create(sourcemapPaths: string[]): Promise<SourceMapper>; | ||
/** | ||
* @param {Array.<string>} sourcemapPaths An array of paths to .map sourcemap | ||
* files that should be processed. The paths should be relative to the | ||
* current process's current working directory | ||
* @param {Logger} logger A logger that reports errors that occurred while | ||
* processing the given sourcemap files | ||
*/ | ||
export declare function create(sourcemapPaths: string[], logger: Logger): Promise<SourceMapper>; |
@@ -120,4 +120,6 @@ "use strict"; | ||
for (const src of normalizedSourcesRelToProc) { | ||
infoMap.set(path.normalize(src), { | ||
const inputFile = path.normalize(src); | ||
infoMap.set(inputFile, { | ||
outputFile: outputPath, | ||
inputFile, | ||
mapFile: mapPath, | ||
@@ -130,11 +132,4 @@ mapConsumer: consumer, | ||
class SourceMapper { | ||
/** | ||
* @param {Array.<string>} sourcemapPaths An array of paths to .map sourcemap | ||
* files that should be processed. The paths should be relative to the | ||
* current process's current working directory | ||
* @param {Logger} logger A logger that reports errors that occurred while | ||
* processing the given sourcemap files | ||
* @constructor | ||
*/ | ||
constructor() { | ||
constructor(logger) { | ||
this.logger = logger; | ||
this.infoMap = new Map(); | ||
@@ -167,2 +162,3 @@ } | ||
const matches = utils_1.findScriptsFuzzy(inputPath, Array.from(this.infoMap.keys())); | ||
this.logger.debug(`sourcemapper fuzzy matches: ${matches}`); | ||
if (matches.length === 1) { | ||
@@ -177,5 +173,2 @@ return this.infoMap.get(matches[0]); | ||
/** | ||
* @param {string} inputPath The path to an input file that could possibly | ||
* be the input to a transpilation process. The path should be relative to | ||
* the process's current working directory | ||
* @param {number} The line number in the input file where the line number is | ||
@@ -185,2 +178,5 @@ * zero-based. | ||
* specified where the column number is zero-based. | ||
* @param {string} The entry of the source map info in the sourceMapper. Such | ||
* an entry is supposed to be got by the getMapInfoInput method. | ||
* | ||
* @return {Object} The object returned has a "file" attribute for the | ||
@@ -197,7 +193,7 @@ * path of the output file associated with the given input file (where the | ||
*/ | ||
getMapInfoOutput(inputPath, lineNumber, colNumber, entry) { | ||
getMapInfoOutput(lineNumber, colNumber, entry) { | ||
var _a; | ||
inputPath = path.normalize(inputPath); | ||
this.logger.debug(`sourcemapper entry.inputFile: ${entry.inputFile}`); | ||
const relPath = path | ||
.relative(path.dirname(entry.mapFile), inputPath) | ||
.relative(path.dirname(entry.mapFile), entry.inputFile) | ||
.replace(/\\/g, '/'); | ||
@@ -223,2 +219,3 @@ /** | ||
}; | ||
this.logger.debug(`sourcemapper sourcePos: ${JSON.stringify(sourcePos)}`); | ||
const allPos = entry.mapConsumer.allGeneratedPositionsFor(sourcePos); | ||
@@ -240,2 +237,3 @@ /* | ||
: entry.mapConsumer.generatedPositionFor(sourcePos); | ||
this.logger.debug(`sourcemapper mappedPos: ${JSON.stringify(mappedPos)}`); | ||
return { | ||
@@ -252,7 +250,24 @@ file: entry.outputFile, | ||
} | ||
/** Prints the debugging information of the source mapper to the logger. */ | ||
debug() { | ||
this.logger.debug('Printing source mapper debugging information ...'); | ||
for (const [key, value] of this.infoMap) { | ||
this.logger.debug(` source ${key}:`); | ||
this.logger.debug(` outputFile: ${value.outputFile}`); | ||
this.logger.debug(` mapFile: ${value.mapFile}`); | ||
this.logger.debug(` sources: ${value.sources}`); | ||
} | ||
} | ||
} | ||
exports.SourceMapper = SourceMapper; | ||
async function create(sourcemapPaths) { | ||
/** | ||
* @param {Array.<string>} sourcemapPaths An array of paths to .map sourcemap | ||
* files that should be processed. The paths should be relative to the | ||
* current process's current working directory | ||
* @param {Logger} logger A logger that reports errors that occurred while | ||
* processing the given sourcemap files | ||
*/ | ||
async function create(sourcemapPaths, logger) { | ||
const limit = pLimit(CONCURRENCY); | ||
const mapper = new SourceMapper(); | ||
const mapper = new SourceMapper(logger); | ||
const promises = sourcemapPaths.map(path => limit(() => processSourcemap(mapper.infoMap, path))); | ||
@@ -265,2 +280,3 @@ try { | ||
} | ||
mapper.debug(); | ||
return mapper; | ||
@@ -267,0 +283,0 @@ } |
@@ -12,2 +12,21 @@ /// <reference types="node" /> | ||
import { V8Inspector } from './v8inspector'; | ||
/** | ||
* An interface that describes options that set behavior when interacting with | ||
* the V8 Inspector API. | ||
*/ | ||
interface InspectorOptions { | ||
/** | ||
* Whether to add a 'file://' prefix to a URL when setting breakpoints. | ||
*/ | ||
useWellFormattedUrl: boolean; | ||
} | ||
/** Data related to the v8 inspector. */ | ||
interface V8Data { | ||
session: inspector.Session; | ||
inspectorOptions: InspectorOptions; | ||
inspector: V8Inspector; | ||
setBreakpointsParams: { | ||
[v8BreakpointId: string]: inspector.Debugger.SetBreakpointByUrlParameterType; | ||
}; | ||
} | ||
export declare class BreakpointData { | ||
@@ -44,6 +63,7 @@ id: inspector.Debugger.BreakpointId; | ||
numBreakpoints: number; | ||
inspector: V8Inspector; | ||
numBreakpointHitsBeforeReset: number; | ||
v8: V8Data; | ||
constructor(logger: consoleLogLevel.Logger, config: ResolvedDebugAgentConfig, jsFiles: ScanStats, sourcemapper: SourceMapper); | ||
/** Disconnects and marks the current V8 data as not connected. */ | ||
disconnect(): void; | ||
/** Creates a new V8 Debugging session and the related data. */ | ||
private createV8Data; | ||
set(breakpoint: stackdriver.Breakpoint, cb: (err: Error | null) => void): void; | ||
@@ -53,2 +73,3 @@ clear(breakpoint: stackdriver.Breakpoint, cb: (err: Error | null) => void): void; | ||
log(breakpoint: stackdriver.Breakpoint, print: (format: string, exps: string[]) => void, shouldStop: () => boolean): void; | ||
disconnect(): void; | ||
numBreakpoints_(): number; | ||
@@ -72,2 +93,8 @@ numListeners_(): number; | ||
private handleDebugPausedEvent; | ||
/** | ||
* Periodically resets breakpoints to prevent memory leaks in V8 (for holding | ||
* contexts of previous breakpoint hits). | ||
*/ | ||
private tryResetV8Debugger; | ||
} | ||
export {}; |
@@ -18,2 +18,4 @@ "use strict"; | ||
const acorn = require("acorn"); | ||
// eslint-disable-next-line node/no-unsupported-features/node-builtins | ||
const inspector = require("inspector"); | ||
const path = require("path"); | ||
@@ -58,2 +60,3 @@ const status_message_1 = require("../../client/stackdriver/status-message"); | ||
this.numBreakpoints = 0; | ||
this.numBreakpointHitsBeforeReset = 0; | ||
this.logger = logger; | ||
@@ -63,14 +66,17 @@ this.config = config; | ||
this.sourcemapper = sourcemapper; | ||
this.inspector = new v8inspector_1.V8Inspector( | ||
/* logger=*/ logger, | ||
/*useWellFormattedUrl=*/ utils.satisfies(process.version, '>10.11.0'), | ||
/*resetV8DebuggerThreshold=*/ this.config.resetV8DebuggerThreshold, | ||
/*onScriptParsed=*/ | ||
scriptParams => { | ||
this.scriptMapper[scriptParams.scriptId] = scriptParams; | ||
}, | ||
/*onPaused=*/ | ||
messageParams => { | ||
this.scriptMapper = {}; | ||
this.v8 = this.createV8Data(); | ||
} | ||
/** Creates a new V8 Debugging session and the related data. */ | ||
createV8Data() { | ||
const session = new inspector.Session(); | ||
session.connect(); | ||
session.on('Debugger.scriptParsed', script => { | ||
this.scriptMapper[script.params.scriptId] = script.params; | ||
}); | ||
session.post('Debugger.enable'); | ||
session.post('Debugger.setBreakpointsActive', { active: true }); | ||
session.on('Debugger.paused', message => { | ||
try { | ||
this.handleDebugPausedEvent(messageParams); | ||
this.handleDebugPausedEvent(message.params); | ||
} | ||
@@ -81,7 +87,12 @@ catch (error) { | ||
}); | ||
return { | ||
session, | ||
inspectorOptions: { | ||
// Well-Formatted URL is required in Node 10.11.1+. | ||
useWellFormattedUrl: utils.satisfies(process.version, '>10.11.0'), | ||
}, | ||
inspector: new v8inspector_1.V8Inspector(session), | ||
setBreakpointsParams: {}, | ||
}; | ||
} | ||
/** Disconnects and marks the current V8 data as not connected. */ | ||
disconnect() { | ||
this.inspector.detach(); | ||
} | ||
set(breakpoint, cb) { | ||
@@ -119,3 +130,3 @@ if (!breakpoint || | ||
const column = 0; | ||
const mapInfo = this.sourcemapper.getMapInfoOutput(baseScriptPath, line, column, mapInfoInput); | ||
const mapInfo = this.sourcemapper.getMapInfoOutput(line, column, mapInfoInput); | ||
const compile = utils.getBreakpointCompiler(breakpoint); | ||
@@ -157,3 +168,4 @@ if (breakpoint.condition && compile) { | ||
// id, we should remove this breakpoint from v8. | ||
result = this.inspector.removeBreakpoint(breakpointData.id); | ||
result = this.v8.inspector.removeBreakpoint(breakpointData.id); | ||
delete this.v8.setBreakpointsParams[breakpointData.id]; | ||
} | ||
@@ -218,2 +230,5 @@ delete this.breakpoints[breakpoint.id]; | ||
} | ||
disconnect() { | ||
this.v8.session.disconnect(); | ||
} | ||
numBreakpoints_() { | ||
@@ -336,3 +351,3 @@ // Tracks the number of stackdriver breakpoints. | ||
// The first time when a breakpoint was set to this location. | ||
const rawUrl = this.inspector.shouldUseWellFormattedUrl() | ||
const rawUrl = this.v8.inspectorOptions.useWellFormattedUrl | ||
? `file://${matchingScript}` | ||
@@ -345,3 +360,3 @@ : matchingScript; | ||
: rawUrl; | ||
const res = this.inspector.setBreakpointByUrl({ | ||
const params = { | ||
lineNumber: line - 1, | ||
@@ -351,3 +366,4 @@ url, | ||
condition: breakpoint.condition || undefined, | ||
}); | ||
}; | ||
const res = this.v8.inspector.setBreakpointByUrl(params); | ||
if (res.error || !res.response) { | ||
@@ -358,2 +374,3 @@ // Error case. | ||
v8BreakpointId = res.response.breakpointId; | ||
this.v8.setBreakpointsParams[v8BreakpointId] = params; | ||
this.locationMapper[locationStr] = []; | ||
@@ -423,3 +440,3 @@ this.breakpointMapper[v8BreakpointId] = []; | ||
// value will be returned to log. | ||
const result = state.evaluate(exp, frame, that.inspector, true); | ||
const result = state.evaluate(exp, frame, that.v8.inspector, true); | ||
if (result.error) { | ||
@@ -436,3 +453,3 @@ return result.error; | ||
else { | ||
const captured = state.capture(callFrames, breakpoint, this.config, this.scriptMapper, this.inspector); | ||
const captured = state.capture(callFrames, breakpoint, this.config, this.scriptMapper, this.v8.inspector); | ||
if (breakpoint.location && | ||
@@ -465,5 +482,30 @@ utils.isJavaScriptFile(breakpoint.location.path)) { | ||
} | ||
this.tryResetV8Debugger(); | ||
} | ||
/** | ||
* Periodically resets breakpoints to prevent memory leaks in V8 (for holding | ||
* contexts of previous breakpoint hits). | ||
*/ | ||
tryResetV8Debugger() { | ||
this.numBreakpointHitsBeforeReset += 1; | ||
if (this.numBreakpointHitsBeforeReset < this.config.resetV8DebuggerThreshold) { | ||
return; | ||
} | ||
this.numBreakpointHitsBeforeReset = 0; | ||
const storedParams = this.v8.setBreakpointsParams; | ||
// Re-connect the session to clean the memory usage. | ||
this.disconnect(); | ||
this.scriptMapper = {}; | ||
this.v8 = this.createV8Data(); | ||
this.v8.setBreakpointsParams = storedParams; | ||
// Setting the v8 breakpoints again according to the stored parameters. | ||
for (const params of Object.values(storedParams)) { | ||
const res = this.v8.inspector.setBreakpointByUrl(params); | ||
if (res.error || !res.response) { | ||
this.logger.error('Error upon re-setting breakpoint: ' + res); | ||
} | ||
} | ||
} | ||
} | ||
exports.InspectorDebugApi = InspectorDebugApi; | ||
//# sourceMappingURL=inspector-debugapi.js.map |
@@ -98,3 +98,3 @@ "use strict"; | ||
const column = 0; | ||
const mapInfo = this.sourcemapper.getMapInfoOutput(baseScriptPath, line, column, mapInfoInput); | ||
const mapInfo = this.sourcemapper.getMapInfoOutput(line, column, mapInfoInput); | ||
const compile = utils.getBreakpointCompiler(breakpoint); | ||
@@ -101,0 +101,0 @@ if (breakpoint.condition && compile) { |
/// <reference types="node" /> | ||
import * as inspector from 'inspector'; | ||
import consoleLogLevel = require('console-log-level'); | ||
export declare class V8Inspector { | ||
readonly logger: consoleLogLevel.Logger; | ||
readonly useWellFormattedUrl: boolean; | ||
readonly resetV8DebuggerThreshold: number; | ||
readonly onScriptParsed: (script: inspector.Debugger.ScriptParsedEventDataType) => void; | ||
readonly onPaused: (params: inspector.Debugger.PausedEventDataType) => void; | ||
session: inspector.Session | null; | ||
storeSetBreakpointParams: { | ||
[v8BreakpointId: string]: inspector.Debugger.SetBreakpointByUrlParameterType; | ||
}; | ||
numPausedBeforeReset: number; | ||
constructor(logger: consoleLogLevel.Logger, useWellFormattedUrl: boolean, resetV8DebuggerThreshold: number, onScriptParsed: (script: inspector.Debugger.ScriptParsedEventDataType) => void, onPaused: (params: inspector.Debugger.PausedEventDataType) => void); | ||
/** | ||
* Whether to add a 'file://' prefix to a URL when setting breakpoints. | ||
*/ | ||
shouldUseWellFormattedUrl(): boolean; | ||
setBreakpointByUrl(params: inspector.Debugger.SetBreakpointByUrlParameterType): { | ||
private session; | ||
constructor(session: inspector.Session); | ||
setBreakpointByUrl(options: inspector.Debugger.SetBreakpointByUrlParameterType): { | ||
error?: Error | undefined; | ||
@@ -35,15 +21,2 @@ response?: inspector.Debugger.SetBreakpointByUrlReturnType | undefined; | ||
}; | ||
/** Attaches to the V8 debugger. */ | ||
private attach; | ||
/** | ||
* Detaches from the V8 debugger. This will purge all the existing V8 | ||
* breakpoints from the V8 debugger. | ||
*/ | ||
detach(): void; | ||
/** | ||
* Resets the debugging session when the number of paused events meets the | ||
* threshold. This is primarily for cleaning the memory usage hold by V8 | ||
* debugger when hitting the V8 breakpoints too many times. | ||
*/ | ||
private resetV8DebuggerIfThresholdMet; | ||
} |
@@ -17,35 +17,11 @@ "use strict"; | ||
exports.V8Inspector = void 0; | ||
// eslint-disable-next-line node/no-unsupported-features/node-builtins | ||
const inspector = require("inspector"); | ||
class V8Inspector { | ||
constructor(logger, useWellFormattedUrl, resetV8DebuggerThreshold, onScriptParsed, onPaused) { | ||
this.logger = logger; | ||
this.useWellFormattedUrl = useWellFormattedUrl; | ||
this.resetV8DebuggerThreshold = resetV8DebuggerThreshold; | ||
this.onScriptParsed = onScriptParsed; | ||
this.onPaused = onPaused; | ||
// The V8 debugger session. | ||
this.session = null; | ||
// Store of the v8 setBreakpoint parameters for each v8 breakpoint so that | ||
// later the recorded parameters can be used to reset the breakpoints. | ||
this.storeSetBreakpointParams = {}; | ||
// Number of paused events before the next reset. | ||
this.numPausedBeforeReset = 0; | ||
constructor(session) { | ||
this.session = session; | ||
} | ||
/** | ||
* Whether to add a 'file://' prefix to a URL when setting breakpoints. | ||
*/ | ||
shouldUseWellFormattedUrl() { | ||
return this.useWellFormattedUrl; | ||
} | ||
setBreakpointByUrl(params) { | ||
this.attach(); | ||
setBreakpointByUrl(options) { | ||
const result = {}; | ||
this.session.post('Debugger.setBreakpointByUrl', params, (error, response) => { | ||
if (error) { | ||
this.session.post('Debugger.setBreakpointByUrl', options, (error, response) => { | ||
if (error) | ||
result.error = error; | ||
} | ||
else { | ||
this.storeSetBreakpointParams[response.breakpointId] = params; | ||
} | ||
result.response = response; | ||
@@ -56,15 +32,6 @@ }); | ||
removeBreakpoint(breakpointId) { | ||
this.attach(); | ||
const result = {}; | ||
this.session.post('Debugger.removeBreakpoint', { breakpointId }, (error) => { | ||
if (error) { | ||
if (error) | ||
result.error = error; | ||
} | ||
else { | ||
delete this.storeSetBreakpointParams[breakpointId]; | ||
} | ||
// If there is no active V8 breakpoints, then detach the session. | ||
if (Object.keys(this.storeSetBreakpointParams).length === 0) { | ||
this.detach(); | ||
} | ||
}); | ||
@@ -74,3 +41,2 @@ return result; | ||
evaluateOnCallFrame(options) { | ||
this.attach(); | ||
const result = {}; | ||
@@ -86,3 +52,2 @@ this.session.post('Debugger.evaluateOnCallFrame', options, (error, response) => { | ||
const result = {}; | ||
this.attach(); | ||
this.session.post('Runtime.getProperties', options, (error, response) => { | ||
@@ -95,57 +60,4 @@ if (error) | ||
} | ||
/** Attaches to the V8 debugger. */ | ||
attach() { | ||
if (this.session) { | ||
return; | ||
} | ||
const session = new inspector.Session(); | ||
session.connect(); | ||
session.on('Debugger.scriptParsed', script => { | ||
this.onScriptParsed(script.params); | ||
}); | ||
session.post('Debugger.enable'); | ||
session.post('Debugger.setBreakpointsActive', { active: true }); | ||
session.on('Debugger.paused', message => { | ||
this.onPaused(message.params); | ||
this.resetV8DebuggerIfThresholdMet(); | ||
}); | ||
this.session = session; | ||
} | ||
/** | ||
* Detaches from the V8 debugger. This will purge all the existing V8 | ||
* breakpoints from the V8 debugger. | ||
*/ | ||
detach() { | ||
if (!this.session) { | ||
return; | ||
} | ||
this.session.disconnect(); | ||
this.session = null; | ||
this.storeSetBreakpointParams = {}; | ||
this.numPausedBeforeReset = 0; | ||
} | ||
/** | ||
* Resets the debugging session when the number of paused events meets the | ||
* threshold. This is primarily for cleaning the memory usage hold by V8 | ||
* debugger when hitting the V8 breakpoints too many times. | ||
*/ | ||
resetV8DebuggerIfThresholdMet() { | ||
this.numPausedBeforeReset += 1; | ||
if (this.numPausedBeforeReset < this.resetV8DebuggerThreshold) { | ||
return; | ||
} | ||
this.numPausedBeforeReset = 0; | ||
const previouslyStoredParams = this.storeSetBreakpointParams; | ||
this.detach(); | ||
this.attach(); | ||
// Setting the v8 breakpoints again according to the stored parameters. | ||
for (const params of Object.values(previouslyStoredParams)) { | ||
const res = this.setBreakpointByUrl(params); | ||
if (res.error || !res.response) { | ||
this.logger.error('Error upon re-setting breakpoint: ' + res); | ||
} | ||
} | ||
} | ||
} | ||
exports.V8Inspector = V8Inspector; | ||
//# sourceMappingURL=v8inspector.js.map |
{ | ||
"name": "@google-cloud/debug-agent", | ||
"version": "5.2.4", | ||
"version": "5.2.5", | ||
"author": "Google Inc.", | ||
@@ -5,0 +5,0 @@ "description": "Stackdriver Debug Agent for Node.js", |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is too big to display
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
License Policy Violation
LicenseThis package is not allowed per your license policy. Review the package's license to ensure compliance.
Found 1 instance in 1 package
451857
5794