Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SDK-2492 npm checker #37

Merged
merged 5 commits into from
Aug 7, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions config/checkers.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,6 @@ parameters:
minimum_allowed_shop_version_checker_doc_url: https://docs.spryker.com/docs/scos/dev/guidelines/keeping-a-project-upgradable/upgradability-guidelines/minimum-allowed-shop-version.html
minimum_allowed_shop_version_checker_version: '202204.0'
minimum_allowed_shop_version_checker_file: '%evaluator_dir%/data/minimum-allowed-package-versions.json'

# Npm checker
npm_checker_doc_url: https://docs.spryker.com/docs/scos/dev/guidelines/keeping-a-project-upgradable/upgradability-guidelines/npm-checker.html
4 changes: 4 additions & 0 deletions config/services.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -127,6 +127,10 @@ services:
arguments:
$checkerDocUrl: '%single_plugin_argument_checker_doc_url%'

SprykerSdk\Evaluator\Checker\NpmChecker\NpmChecker:
arguments:
$checkerDocUrl: '%npm_checker_doc_url%'

# Third-party
PhpParser\ParserFactory: ~
Symfony\Component\Finder\Finder: ~
Expand Down
21 changes: 21 additions & 0 deletions src/Checker/AbstractChecker.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

declare(strict_types=1);

namespace SprykerSdk\Evaluator\Checker;

abstract class AbstractChecker implements CheckerInterface
{
/**
* @return bool
*/
public function isApplicable(): bool
{
return true;
}
}
5 changes: 5 additions & 0 deletions src/Checker/CheckerInterface.php
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@

interface CheckerInterface
{
/**
* @return bool
*/
public function isApplicable(): bool;

/**
* @param \SprykerSdk\Evaluator\Dto\CheckerInputDataDto $inputData
*
Expand Down
4 changes: 2 additions & 2 deletions src/Checker/DeadCode/DeadCodeChecker.php
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,12 @@

namespace SprykerSdk\Evaluator\Checker\DeadCode;

use SprykerSdk\Evaluator\Checker\CheckerInterface;
use SprykerSdk\Evaluator\Checker\AbstractChecker;
use SprykerSdk\Evaluator\Dto\CheckerInputDataDto;
use SprykerSdk\Evaluator\Dto\CheckerResponseDto;
use SprykerSdk\Evaluator\Dto\ViolationDto;

class DeadCodeChecker implements CheckerInterface
class DeadCodeChecker extends AbstractChecker
{
/**
* @var string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,15 +15,15 @@
use PhpParser\Node\Name;
use PhpParser\Node\Stmt\If_;
use PhpParser\NodeFinder;
use SprykerSdk\Evaluator\Checker\CheckerInterface;
use SprykerSdk\Evaluator\Checker\AbstractChecker;
use SprykerSdk\Evaluator\Dto\CheckerInputDataDto;
use SprykerSdk\Evaluator\Dto\CheckerResponseDto;
use SprykerSdk\Evaluator\Dto\ViolationDto;
use SprykerSdk\Evaluator\Finder\SourceFinderInterface;
use SprykerSdk\Evaluator\Parser\PhpParserInterface;
use Symfony\Component\Finder\Finder;

class DependencyProviderAdditionalLogicChecker implements CheckerInterface
class DependencyProviderAdditionalLogicChecker extends AbstractChecker
{
/**
* @var string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -9,13 +9,13 @@

namespace SprykerSdk\Evaluator\Checker\MinimumShopVersionChecker;

use SprykerSdk\Evaluator\Checker\CheckerInterface;
use SprykerSdk\Evaluator\Checker\AbstractChecker;
use SprykerSdk\Evaluator\Dto\CheckerInputDataDto;
use SprykerSdk\Evaluator\Dto\CheckerResponseDto;
use SprykerSdk\Evaluator\Dto\ViolationDto;
use SprykerSdk\Evaluator\Reader\ComposerReaderInterface;

class MinimumShopVersionChecker implements CheckerInterface
class MinimumShopVersionChecker extends AbstractChecker
{
/**
* @var string
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@
namespace SprykerSdk\Evaluator\Checker\MultidimensionalArrayChecker;

use PhpParser\Node\Stmt\ClassMethod;
use SprykerSdk\Evaluator\Checker\CheckerInterface;
use SprykerSdk\Evaluator\Checker\AbstractChecker;
use SprykerSdk\Evaluator\Dto\CheckerInputDataDto;
use SprykerSdk\Evaluator\Dto\CheckerResponseDto;
use SprykerSdk\Evaluator\Dto\ViolationDto;
Expand All @@ -19,7 +19,7 @@
use SprykerSdk\Evaluator\Parser\PhpParserInterface;
use Symfony\Component\Finder\Finder;

class MultidimensionalArrayChecker implements CheckerInterface
class MultidimensionalArrayChecker extends AbstractChecker
{
/**
* @var string
Expand Down
236 changes: 236 additions & 0 deletions src/Checker/NpmChecker/NpmAuditExecutor.php
Original file line number Diff line number Diff line change
@@ -0,0 +1,236 @@
<?php

/**
* Copyright © 2016-present Spryker Systems GmbH. All rights reserved.
* Use of this software requires acceptance of the Evaluation License Agreement. See LICENSE file.
*/

declare(strict_types=1);

namespace SprykerSdk\Evaluator\Checker\NpmChecker;

use JsonException;
use SprykerSdk\Evaluator\Dto\ViolationDto;
use SprykerSdk\Evaluator\Process\ProcessRunnerInterface;

class NpmAuditExecutor
{
/**
* @var string
*/
protected const VULNERABILITIES_KEY = 'vulnerabilities';

/**
* @var array<string> to skip info level
*/
protected const ALLOWED_SEVERITY_LEVELS = ['low', 'moderate', 'high', 'critical'];

/**
* @var string
*/
protected const DEFAULT_AUDIT_LEVEL = 'low';

/**
* @var string
*/
protected const SEVERITY_KEY = 'severity';

/**
* @var string
*/
protected const NAME_KEY = 'name';

/**
* @var string
*/
protected const VIA_KEY = 'via';

/**
* @var string
*/
protected const TITLE_KEY = 'title';

/**
* @var string
*/
protected const URL_KEY = 'url';

/**
* @var \SprykerSdk\Evaluator\Process\ProcessRunnerInterface
*/
private ProcessRunnerInterface $processRunner;

/**
* @param \SprykerSdk\Evaluator\Process\ProcessRunnerInterface $processRunner
*/
public function __construct(ProcessRunnerInterface $processRunner)
{
$this->processRunner = $processRunner;
}

/**
* @throws \SprykerSdk\Evaluator\Checker\NpmChecker\NpmExecutorException
*
* @return array<\SprykerSdk\Evaluator\Dto\ViolationDto>
*/
public function executeNpmAudit(): array
{
$process = $this->processRunner->run(['npm', 'audit', '--json', '--audit-level', static::DEFAULT_AUDIT_LEVEL]);

if ($process->isSuccessful()) {
return [];
}

$stdOut = trim($process->getOutput());
$stdErr = trim($process->getErrorOutput());

if ($stdErr && !$stdOut) {
throw new NpmExecutorException($stdErr);
}

try {
$report = json_decode($stdOut, true, 512, \JSON_THROW_ON_ERROR);
} catch (JsonException $e) {
throw new NpmExecutorException(sprintf('Out: %s Err: %s', $stdOut, $stdErr));
}

$this->assertKeyExists($report, static::VULNERABILITIES_KEY);
$this->assertArray($report[static::VULNERABILITIES_KEY]);

return $this->getUniqueViolations(
$this->getViolationsFromReport($report[static::VULNERABILITIES_KEY]),
);
}

/**
* @param array<mixed> $violations
*
* @return array<\SprykerSdk\Evaluator\Dto\ViolationDto>
*/
protected function getViolationsFromReport(array $violations): array
{
$violationDtos = [];

foreach ($violations as $violation) {
$this->assertViolationData($violation);

$severity = $violation[static::SEVERITY_KEY];

if (!in_array($severity, static::ALLOWED_SEVERITY_LEVELS, true)) {
continue;
}

foreach ($violation[static::VIA_KEY] as $violationSource) {
if (!is_array($violationSource)) {
continue;
}

$violationDto = $this->processViolationSource($violationSource, $severity, $violation[static::NAME_KEY]);

if ($violationDto === null) {
continue;
}

$violationDtos[] = $violationDto;
}
}

return $violationDtos;
}

/**
* @param array<mixed> $violationSource
* @param string $severity
* @param string $target
*
* @return \SprykerSdk\Evaluator\Dto\ViolationDto|null
*/
protected function processViolationSource(array $violationSource, string $severity, string $target): ?ViolationDto
{
if (!isset($violationSource[static::TITLE_KEY])) {
return null;
}

$message = $violationSource[static::TITLE_KEY];

if (isset($violationSource[static::URL_KEY])) {
$message .= PHP_EOL . $violationSource[static::URL_KEY];
}

return new ViolationDto(sprintf('[%s] %s', $severity, $message), $target);
}

/**
* @param array<\SprykerSdk\Evaluator\Dto\ViolationDto> $violations
*
* @return array<\SprykerSdk\Evaluator\Dto\ViolationDto>
*/
protected function getUniqueViolations(array $violations): array
{
$uniqueViolations = [];
$processedViolations = [];

foreach ($violations as $violation) {
$violationHash = sha1($violation->getTarget() . $violation->getMessage());

if (in_array($violationHash, $processedViolations, true)) {
continue;
}

$uniqueViolations[] = $violation;
$processedViolations[] = $violationHash;
}

return $uniqueViolations;
}

/**
* @param array<mixed> $violation
*
* @return void
*/
protected function assertViolationData(array $violation): void
{
$this->assertKeyExists($violation, static::SEVERITY_KEY);
$this->assertKeyExists($violation, static::NAME_KEY);
$this->assertKeyExists($violation, static::VIA_KEY);
$this->assertArray($violation[static::VIA_KEY]);
}

/**
* @param array<mixed> $report
* @param string $key
*
* @throws \SprykerSdk\Evaluator\Checker\NpmChecker\NpmExecutorException
*
* @return void
*/
protected function assertKeyExists(array $report, string $key): void
{
if (isset($report[$key])) {
return;
}

throw new NpmExecutorException(
sprintf('Unable to find "%s" key in output array "%s"', $key, substr(var_export($report, true), 0, 500)),
);
}

/**
* @param mixed $value
*
* @throws \SprykerSdk\Evaluator\Checker\NpmChecker\NpmExecutorException
*
* @return void
*/
protected function assertArray($value): void
{
if (is_array($value)) {
return;
}

throw new NpmExecutorException(
sprintf('Value should be an array %s', substr(var_export($value, true), 0, 500)),
);
}
}
Loading
Loading