mirror of
https://github.com/pestphp/pest.git
synced 2026-06-05 10:52:14 +02:00
wip
This commit is contained in:
@ -479,67 +479,7 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
||||
$changedFiles->snapshotTree($changedFiles->since($currentSha) ?? []),
|
||||
);
|
||||
|
||||
$mergedFiles = [];
|
||||
$mergedTables = [];
|
||||
$mergedInertia = [];
|
||||
|
||||
foreach ($partialKeys as $key) {
|
||||
$data = $this->readPartial($key);
|
||||
|
||||
if ($data === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($data['files'] as $testFile => $sources) {
|
||||
if (! isset($mergedFiles[$testFile])) {
|
||||
$mergedFiles[$testFile] = [];
|
||||
}
|
||||
|
||||
foreach ($sources as $source) {
|
||||
$mergedFiles[$testFile][$source] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['tables'] as $testFile => $tables) {
|
||||
if (! isset($mergedTables[$testFile])) {
|
||||
$mergedTables[$testFile] = [];
|
||||
}
|
||||
|
||||
foreach ($tables as $table) {
|
||||
$mergedTables[$testFile][$table] = true;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($data['inertia'] as $testFile => $components) {
|
||||
if (! isset($mergedInertia[$testFile])) {
|
||||
$mergedInertia[$testFile] = [];
|
||||
}
|
||||
|
||||
foreach ($components as $component) {
|
||||
$mergedInertia[$testFile][$component] = true;
|
||||
}
|
||||
}
|
||||
|
||||
$this->state->delete($key);
|
||||
}
|
||||
|
||||
$finalised = [];
|
||||
|
||||
foreach ($mergedFiles as $testFile => $sourceSet) {
|
||||
$finalised[$testFile] = array_keys($sourceSet);
|
||||
}
|
||||
|
||||
$finalisedTables = [];
|
||||
|
||||
foreach ($mergedTables as $testFile => $tableSet) {
|
||||
$finalisedTables[$testFile] = array_keys($tableSet);
|
||||
}
|
||||
|
||||
$finalisedInertia = [];
|
||||
|
||||
foreach ($mergedInertia as $testFile => $componentSet) {
|
||||
$finalisedInertia[$testFile] = array_keys($componentSet);
|
||||
}
|
||||
[$finalised, $finalisedTables, $finalisedInertia] = $this->consumePartials($partialKeys);
|
||||
|
||||
if ($finalised === []) {
|
||||
if ($this->replayRan) {
|
||||
@ -1201,6 +1141,43 @@ final class Tia implements AddsOutput, HandlesArguments, Terminable
|
||||
return $token;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $partialKeys
|
||||
* @return array{0: array<string, list<string>>, 1: array<string, list<string>>, 2: array<string, list<string>>}
|
||||
*/
|
||||
private function consumePartials(array $partialKeys): array
|
||||
{
|
||||
$merged = ['files' => [], 'tables' => [], 'inertia' => []];
|
||||
|
||||
foreach ($partialKeys as $key) {
|
||||
$data = $this->readPartial($key);
|
||||
|
||||
if ($data === null) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach (['files', 'tables', 'inertia'] as $section) {
|
||||
foreach ($data[$section] as $testFile => $values) {
|
||||
if (! isset($merged[$section][$testFile])) {
|
||||
$merged[$section][$testFile] = [];
|
||||
}
|
||||
|
||||
foreach ($values as $value) {
|
||||
$merged[$section][$testFile][$value] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$this->state->delete($key);
|
||||
}
|
||||
|
||||
return [
|
||||
array_map(array_keys(...), $merged['files']),
|
||||
array_map(array_keys(...), $merged['tables']),
|
||||
array_map(array_keys(...), $merged['inertia']),
|
||||
];
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{files: array<string, array<int, string>>, tables: array<string, array<int, string>>, inertia: array<string, array<int, string>>}|null
|
||||
*/
|
||||
|
||||
@ -289,21 +289,7 @@ YAML;
|
||||
{
|
||||
$failureKind = null;
|
||||
|
||||
if (! $this->commandExists('gh')) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
'GitHub CLI (gh) not found — cannot fetch baseline.',
|
||||
'Install it from https://cli.github.com.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
|
||||
if (! $this->ghAuthenticated()) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
'GitHub CLI (gh) is not authenticated — cannot fetch baseline.',
|
||||
'Run `gh auth login` and retry.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
$this->validateGhDependencies($hasAnchor);
|
||||
|
||||
[$runId, $listError] = $this->latestSuccessfulRunIdWithError($repo);
|
||||
|
||||
@ -350,6 +336,41 @@ YAML;
|
||||
return null;
|
||||
}
|
||||
|
||||
if (! $this->downloadArtifact($repo, $runId, $runCacheDir, $hasAnchor, $failureKind)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
$payload = $this->validateDownloadedArtifact($runCacheDir, $hasAnchor);
|
||||
|
||||
$this->trimDownloadCache($projectRoot);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
private function validateGhDependencies(bool $hasAnchor): void
|
||||
{
|
||||
if (! $this->commandExists('gh')) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
'GitHub CLI (gh) not found — cannot fetch baseline.',
|
||||
'Install it from https://cli.github.com.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
|
||||
if (! $this->ghAuthenticated()) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
'GitHub CLI (gh) is not authenticated — cannot fetch baseline.',
|
||||
'Run `gh auth login` and retry.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param-out string|null $failureKind
|
||||
*/
|
||||
private function downloadArtifact(string $repo, string $runId, string $runCacheDir, bool $hasAnchor, ?string &$failureKind): bool
|
||||
{
|
||||
$artifactSize = $this->artifactSize($repo, $runId);
|
||||
|
||||
$this->renderBadge('INFO', $artifactSize !== null
|
||||
@ -382,28 +403,36 @@ YAML;
|
||||
$process->wait();
|
||||
$this->clearProgressLine();
|
||||
|
||||
if (! $process->isSuccessful()) {
|
||||
$this->cleanup($runCacheDir);
|
||||
|
||||
$diagnosis = $this->classifyGhError($process->getErrorOutput().$process->getOutput());
|
||||
$failureKind = $diagnosis['kind'];
|
||||
|
||||
if (in_array($failureKind, ['forbidden', 'not-found'], true)) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
sprintf('Baseline download failed — %s', $diagnosis['message']),
|
||||
'Verify workflow tia-baseline.yml, artifact pest-tia-baseline, and gh token scope.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
|
||||
$this->renderBadge('WARN', sprintf(
|
||||
'Baseline download failed — %s',
|
||||
$diagnosis['message'],
|
||||
));
|
||||
|
||||
return null;
|
||||
if ($process->isSuccessful()) {
|
||||
return true;
|
||||
}
|
||||
|
||||
$this->cleanup($runCacheDir);
|
||||
|
||||
$diagnosis = $this->classifyGhError($process->getErrorOutput().$process->getOutput());
|
||||
$failureKind = $diagnosis['kind'];
|
||||
|
||||
if (in_array($failureKind, ['forbidden', 'not-found'], true)) {
|
||||
Panic::with(new BaselineFetchFailed(
|
||||
sprintf('Baseline download failed — %s', $diagnosis['message']),
|
||||
'Verify workflow tia-baseline.yml, artifact pest-tia-baseline, and gh token scope.',
|
||||
$hasAnchor,
|
||||
));
|
||||
}
|
||||
|
||||
$this->renderBadge('WARN', sprintf(
|
||||
'Baseline download failed — %s',
|
||||
$diagnosis['message'],
|
||||
));
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array{graph: string, coverage: ?string, sizeOnDisk: int}
|
||||
*/
|
||||
private function validateDownloadedArtifact(string $runCacheDir, bool $hasAnchor): array
|
||||
{
|
||||
$payload = $this->readArtifact($runCacheDir);
|
||||
|
||||
if ($payload === null) {
|
||||
@ -416,8 +445,6 @@ YAML;
|
||||
));
|
||||
}
|
||||
|
||||
$this->trimDownloadCache($projectRoot);
|
||||
|
||||
return $payload;
|
||||
}
|
||||
|
||||
|
||||
@ -64,30 +64,11 @@ final readonly class Fingerprint
|
||||
*/
|
||||
public static function structuralDrift(array $stored, array $current): array
|
||||
{
|
||||
$a = self::structuralOnly($stored);
|
||||
$b = self::structuralOnly($current);
|
||||
|
||||
$drifts = [];
|
||||
|
||||
foreach ($a as $key => $value) {
|
||||
if ($key === 'schema') {
|
||||
continue;
|
||||
}
|
||||
if (($b[$key] ?? null) !== $value) {
|
||||
$drifts[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($b as $key => $value) {
|
||||
if ($key === 'schema') {
|
||||
continue;
|
||||
}
|
||||
if (! array_key_exists($key, $a) && $value !== null) {
|
||||
$drifts[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
return array_values(array_unique($drifts));
|
||||
return self::detectDrift(
|
||||
self::structuralOnly($stored),
|
||||
self::structuralOnly($current),
|
||||
'schema',
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -97,18 +78,34 @@ final readonly class Fingerprint
|
||||
*/
|
||||
public static function environmentalDrift(array $stored, array $current): array
|
||||
{
|
||||
$a = self::environmentalOnly($stored);
|
||||
$b = self::environmentalOnly($current);
|
||||
return self::detectDrift(
|
||||
self::environmentalOnly($stored),
|
||||
self::environmentalOnly($current),
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<string, mixed> $a
|
||||
* @param array<string, mixed> $b
|
||||
* @return list<string>
|
||||
*/
|
||||
private static function detectDrift(array $a, array $b, ?string $skipKey = null): array
|
||||
{
|
||||
$drifts = [];
|
||||
|
||||
foreach ($a as $key => $value) {
|
||||
if ($key === $skipKey) {
|
||||
continue;
|
||||
}
|
||||
if (($b[$key] ?? null) !== $value) {
|
||||
$drifts[] = $key;
|
||||
}
|
||||
}
|
||||
|
||||
foreach ($b as $key => $value) {
|
||||
if ($key === $skipKey) {
|
||||
continue;
|
||||
}
|
||||
if (! array_key_exists($key, $a) && $value !== null) {
|
||||
$drifts[] = $key;
|
||||
}
|
||||
|
||||
@ -86,37 +86,76 @@ final class Graph
|
||||
*/
|
||||
public function affected(array $changedFiles): array
|
||||
{
|
||||
$normalised = [];
|
||||
[$migrationPaths, $nonMigrationPaths] = $this->partitionChangedPaths($changedFiles);
|
||||
|
||||
$affectedSet = [];
|
||||
|
||||
$unparseableMigrations = $this->applyMigrationChanges($migrationPaths, $affectedSet);
|
||||
|
||||
[$globalFrontendRuntimeFiles, $preciselyHandledPages, $sharedFilesResolved]
|
||||
= $this->applyInertiaChanges($nonMigrationPaths, $affectedSet);
|
||||
|
||||
$unknownSourceDirs = $this->applyPhpEdgeChanges($nonMigrationPaths, $affectedSet);
|
||||
|
||||
$this->applyTestFileChanges($nonMigrationPaths, $affectedSet);
|
||||
|
||||
$staticallyHandledBlade = $this->applyBladeStaticChanges($nonMigrationPaths, $affectedSet);
|
||||
|
||||
$this->applyWatchPatternFallback(
|
||||
$nonMigrationPaths,
|
||||
$unparseableMigrations,
|
||||
$preciselyHandledPages,
|
||||
$sharedFilesResolved,
|
||||
$staticallyHandledBlade,
|
||||
$affectedSet,
|
||||
);
|
||||
|
||||
$this->applyUnknownSourceDirs($unknownSourceDirs, $affectedSet);
|
||||
|
||||
return array_keys($affectedSet);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param array<int, string> $changedFiles
|
||||
* @return array{0: list<string>, 1: list<string>}
|
||||
*/
|
||||
private function partitionChangedPaths(array $changedFiles): array
|
||||
{
|
||||
$migrations = [];
|
||||
$nonMigrations = [];
|
||||
|
||||
foreach ($changedFiles as $file) {
|
||||
$rel = $this->relative($file);
|
||||
|
||||
if ($rel !== null) {
|
||||
$normalised[] = $rel;
|
||||
if ($rel === null) {
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
$affectedSet = [];
|
||||
|
||||
$migrationPaths = [];
|
||||
$nonMigrationPaths = [];
|
||||
|
||||
foreach ($normalised as $rel) {
|
||||
if ($this->isMigrationPath($rel)) {
|
||||
$migrationPaths[] = $rel;
|
||||
$migrations[] = $rel;
|
||||
} else {
|
||||
$nonMigrationPaths[] = $rel;
|
||||
$nonMigrations[] = $rel;
|
||||
}
|
||||
}
|
||||
|
||||
return [$migrations, $nonMigrations];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $migrationPaths
|
||||
* @param array<string, true> $affectedSet
|
||||
* @return list<string> Unparseable migrations (caller treats as unknown-to-graph).
|
||||
*/
|
||||
private function applyMigrationChanges(array $migrationPaths, array &$affectedSet): array
|
||||
{
|
||||
$changedTables = [];
|
||||
$unparseableMigrations = [];
|
||||
$unparseable = [];
|
||||
|
||||
foreach ($migrationPaths as $rel) {
|
||||
$tables = $this->tablesForMigration($rel);
|
||||
|
||||
if ($tables === []) {
|
||||
$unparseableMigrations[] = $rel;
|
||||
$unparseable[] = $rel;
|
||||
|
||||
continue;
|
||||
}
|
||||
@ -142,6 +181,17 @@ final class Graph
|
||||
}
|
||||
}
|
||||
|
||||
return $unparseable;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $nonMigrationPaths
|
||||
* @param array<string, true> $affectedSet
|
||||
* @return array{0: array<string, true>, 1: array<string, true>, 2: array<string, true>}
|
||||
* globalFrontendRuntimeFiles, preciselyHandledPages, sharedFilesResolved
|
||||
*/
|
||||
private function applyInertiaChanges(array $nonMigrationPaths, array &$affectedSet): array
|
||||
{
|
||||
$globalFrontendRuntimeFiles = [];
|
||||
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
@ -173,6 +223,7 @@ final class Graph
|
||||
}
|
||||
|
||||
$sharedFilesResolved = [];
|
||||
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
if (isset($globalFrontendRuntimeFiles[$rel])) {
|
||||
continue;
|
||||
@ -180,12 +231,12 @@ final class Graph
|
||||
if (isset($preciselyHandledPages[$rel])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
if (! isset($this->jsFileToComponents[$rel])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$touchedAny = false;
|
||||
|
||||
foreach ($this->jsFileToComponents[$rel] as $pageComponent) {
|
||||
if ($this->anyTestUses($this->testInertiaComponents, $pageComponent)) {
|
||||
$changedComponents[$pageComponent] = true;
|
||||
@ -199,6 +250,7 @@ final class Graph
|
||||
}
|
||||
|
||||
$newJsFiles = [];
|
||||
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
if (isset($globalFrontendRuntimeFiles[$rel])) {
|
||||
continue;
|
||||
@ -219,39 +271,7 @@ final class Graph
|
||||
}
|
||||
|
||||
if ($newJsFiles !== []) {
|
||||
$freshMap = JsModuleGraph::buildStrict($this->projectRoot);
|
||||
|
||||
if ($freshMap === null) {
|
||||
View::render('components.badge', [
|
||||
'type' => 'WARN',
|
||||
'content' => sprintf(
|
||||
'Vite resolver unavailable — falling back to watch pattern for %d new JS file(s).',
|
||||
count($newJsFiles),
|
||||
),
|
||||
]);
|
||||
} else {
|
||||
foreach ($newJsFiles as $rel) {
|
||||
$pages = $freshMap[$rel] ?? [];
|
||||
|
||||
if ($pages === []) {
|
||||
$sharedFilesResolved[$rel] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$touchedAny = false;
|
||||
foreach ($pages as $pageComponent) {
|
||||
if ($this->anyTestUses($this->testInertiaComponents, $pageComponent)) {
|
||||
$changedComponents[$pageComponent] = true;
|
||||
$touchedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($touchedAny) {
|
||||
$sharedFilesResolved[$rel] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
$this->resolveNewJsFiles($newJsFiles, $changedComponents, $sharedFilesResolved);
|
||||
}
|
||||
|
||||
if ($changedComponents !== []) {
|
||||
@ -270,6 +290,61 @@ final class Graph
|
||||
}
|
||||
}
|
||||
|
||||
return [$globalFrontendRuntimeFiles, $preciselyHandledPages, $sharedFilesResolved];
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $newJsFiles
|
||||
* @param array<string, true> $changedComponents
|
||||
* @param array<string, true> $sharedFilesResolved
|
||||
*/
|
||||
private function resolveNewJsFiles(array $newJsFiles, array &$changedComponents, array &$sharedFilesResolved): void
|
||||
{
|
||||
$freshMap = JsModuleGraph::buildStrict($this->projectRoot);
|
||||
|
||||
if ($freshMap === null) {
|
||||
View::render('components.badge', [
|
||||
'type' => 'WARN',
|
||||
'content' => sprintf(
|
||||
'Vite resolver unavailable — falling back to watch pattern for %d new JS file(s).',
|
||||
count($newJsFiles),
|
||||
),
|
||||
]);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($newJsFiles as $rel) {
|
||||
$pages = $freshMap[$rel] ?? [];
|
||||
|
||||
if ($pages === []) {
|
||||
$sharedFilesResolved[$rel] = true;
|
||||
|
||||
continue;
|
||||
}
|
||||
|
||||
$touchedAny = false;
|
||||
|
||||
foreach ($pages as $pageComponent) {
|
||||
if ($this->anyTestUses($this->testInertiaComponents, $pageComponent)) {
|
||||
$changedComponents[$pageComponent] = true;
|
||||
$touchedAny = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($touchedAny) {
|
||||
$sharedFilesResolved[$rel] = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $nonMigrationPaths
|
||||
* @param array<string, true> $affectedSet
|
||||
* @return array<string, true> Unknown source dirs (sibling-heuristic).
|
||||
*/
|
||||
private function applyPhpEdgeChanges(array $nonMigrationPaths, array &$affectedSet): array
|
||||
{
|
||||
$changedIds = [];
|
||||
$unknownSourceDirs = [];
|
||||
$sourcePhpChanged = false;
|
||||
@ -286,9 +361,7 @@ final class Graph
|
||||
}
|
||||
|
||||
if (str_ends_with($rel, '.php') && ! str_starts_with($rel, 'tests/')) {
|
||||
$absolute = $this->projectRoot.'/'.$rel;
|
||||
|
||||
if (! is_file($absolute)) {
|
||||
if (! is_file($this->projectRoot.'/'.$rel)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
@ -320,8 +393,18 @@ final class Graph
|
||||
}
|
||||
}
|
||||
|
||||
// A changed file inside the configured test suites is itself the unit
|
||||
// of work — always run it (new untracked tests, edited tests, renames).
|
||||
return $unknownSourceDirs;
|
||||
}
|
||||
|
||||
/**
|
||||
* A changed file inside the configured test suites is itself the unit of
|
||||
* work — always run it (new untracked tests, edited tests, renames).
|
||||
*
|
||||
* @param list<string> $nonMigrationPaths
|
||||
* @param array<string, true> $affectedSet
|
||||
*/
|
||||
private function applyTestFileChanges(array $nonMigrationPaths, array &$affectedSet): void
|
||||
{
|
||||
$testPaths = TestPaths::fromProjectRoot($this->projectRoot);
|
||||
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
@ -336,9 +419,19 @@ final class Graph
|
||||
}
|
||||
$affectedSet[$rel] = true;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Unknown Blade files: walk static references (@include, @extends, <x-*>) up to rendered.
|
||||
*
|
||||
* @param list<string> $nonMigrationPaths
|
||||
* @param array<string, true> $affectedSet
|
||||
* @return array<string, true>
|
||||
*/
|
||||
private function applyBladeStaticChanges(array $nonMigrationPaths, array &$affectedSet): array
|
||||
{
|
||||
$staticallyHandled = [];
|
||||
|
||||
// Unknown Blade files: walk static references (@include, @extends, <x-*>) up to rendered
|
||||
$staticallyHandledBlade = [];
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
if (isset($this->fileIds[$rel])) {
|
||||
continue;
|
||||
@ -357,13 +450,33 @@ final class Graph
|
||||
$affectedSet[$testFile] = true;
|
||||
}
|
||||
|
||||
$staticallyHandledBlade[$rel] = true;
|
||||
$staticallyHandled[$rel] = true;
|
||||
} elseif ($this->isBladeComponentPath($rel)) {
|
||||
$staticallyHandledBlade[$rel] = true;
|
||||
$staticallyHandled[$rel] = true;
|
||||
}
|
||||
}
|
||||
|
||||
return $staticallyHandled;
|
||||
}
|
||||
|
||||
/**
|
||||
* @param list<string> $nonMigrationPaths
|
||||
* @param list<string> $unparseableMigrations
|
||||
* @param array<string, true> $preciselyHandledPages
|
||||
* @param array<string, true> $sharedFilesResolved
|
||||
* @param array<string, true> $staticallyHandledBlade
|
||||
* @param array<string, true> $affectedSet
|
||||
*/
|
||||
private function applyWatchPatternFallback(
|
||||
array $nonMigrationPaths,
|
||||
array $unparseableMigrations,
|
||||
array $preciselyHandledPages,
|
||||
array $sharedFilesResolved,
|
||||
array $staticallyHandledBlade,
|
||||
array &$affectedSet,
|
||||
): void {
|
||||
$unknownToGraph = $unparseableMigrations;
|
||||
|
||||
foreach ($nonMigrationPaths as $rel) {
|
||||
if (isset($preciselyHandledPages[$rel])) {
|
||||
continue;
|
||||
@ -392,30 +505,37 @@ final class Graph
|
||||
foreach ($watchPatterns->testsUnderDirectories($dirs, $allTestFiles) as $testFile) {
|
||||
$affectedSet[$testFile] = true;
|
||||
}
|
||||
}
|
||||
|
||||
if ($unknownSourceDirs !== []) {
|
||||
foreach ($this->edges as $testFile => $ids) {
|
||||
if (isset($affectedSet[$testFile])) {
|
||||
/**
|
||||
* @param array<string, true> $unknownSourceDirs
|
||||
* @param array<string, true> $affectedSet
|
||||
*/
|
||||
private function applyUnknownSourceDirs(array $unknownSourceDirs, array &$affectedSet): void
|
||||
{
|
||||
if ($unknownSourceDirs === []) {
|
||||
return;
|
||||
}
|
||||
|
||||
foreach ($this->edges as $testFile => $ids) {
|
||||
if (isset($affectedSet[$testFile])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (! isset($this->files[$id])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
foreach ($ids as $id) {
|
||||
if (! isset($this->files[$id])) {
|
||||
continue;
|
||||
}
|
||||
$depDir = dirname($this->files[$id]);
|
||||
|
||||
$depDir = dirname($this->files[$id]);
|
||||
if (isset($unknownSourceDirs[$depDir])) {
|
||||
$affectedSet[$testFile] = true;
|
||||
|
||||
if (isset($unknownSourceDirs[$depDir])) {
|
||||
$affectedSet[$testFile] = true;
|
||||
|
||||
break;
|
||||
}
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return array_keys($affectedSet);
|
||||
}
|
||||
|
||||
public function knowsTest(string $testFile): bool
|
||||
@ -1229,78 +1349,51 @@ final class Graph
|
||||
$graph->edges = is_array($data['edges'] ?? null) ? $data['edges'] : [];
|
||||
$graph->baselines = is_array($data['baselines'] ?? null) ? $data['baselines'] : [];
|
||||
|
||||
if (isset($data['test_tables']) && is_array($data['test_tables'])) {
|
||||
foreach ($data['test_tables'] as $testRel => $tables) {
|
||||
if (! is_string($testRel)) {
|
||||
continue;
|
||||
}
|
||||
if (! is_array($tables)) {
|
||||
continue;
|
||||
}
|
||||
$names = [];
|
||||
|
||||
foreach ($tables as $table) {
|
||||
if (is_string($table) && $table !== '') {
|
||||
$names[] = $table;
|
||||
}
|
||||
}
|
||||
|
||||
if ($names !== []) {
|
||||
$graph->testTables[$testRel] = $names;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['test_inertia_components']) && is_array($data['test_inertia_components'])) {
|
||||
foreach ($data['test_inertia_components'] as $testRel => $components) {
|
||||
if (! is_string($testRel)) {
|
||||
continue;
|
||||
}
|
||||
if (! is_array($components)) {
|
||||
continue;
|
||||
}
|
||||
$names = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
if (is_string($component) && $component !== '') {
|
||||
$names[] = $component;
|
||||
}
|
||||
}
|
||||
|
||||
if ($names !== []) {
|
||||
$graph->testInertiaComponents[$testRel] = $names;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isset($data['js_file_to_components']) && is_array($data['js_file_to_components'])) {
|
||||
foreach ($data['js_file_to_components'] as $path => $components) {
|
||||
if (! is_string($path)) {
|
||||
continue;
|
||||
}
|
||||
if ($path === '') {
|
||||
continue;
|
||||
}
|
||||
if (! is_array($components)) {
|
||||
continue;
|
||||
}
|
||||
$names = [];
|
||||
|
||||
foreach ($components as $component) {
|
||||
if (is_string($component) && $component !== '') {
|
||||
$names[] = $component;
|
||||
}
|
||||
}
|
||||
|
||||
if ($names !== []) {
|
||||
$graph->jsFileToComponents[$path] = $names;
|
||||
}
|
||||
}
|
||||
}
|
||||
$graph->testTables = self::decodeStringMap($data['test_tables'] ?? null);
|
||||
$graph->testInertiaComponents = self::decodeStringMap($data['test_inertia_components'] ?? null);
|
||||
$graph->jsFileToComponents = self::decodeStringMap($data['js_file_to_components'] ?? null);
|
||||
|
||||
return $graph;
|
||||
}
|
||||
|
||||
/**
|
||||
* @return array<string, list<string>>
|
||||
*/
|
||||
private static function decodeStringMap(mixed $section): array
|
||||
{
|
||||
if (! is_array($section)) {
|
||||
return [];
|
||||
}
|
||||
|
||||
$out = [];
|
||||
|
||||
foreach ($section as $key => $values) {
|
||||
if (! is_string($key)) {
|
||||
continue;
|
||||
}
|
||||
if ($key === '') {
|
||||
continue;
|
||||
}
|
||||
if (! is_array($values)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$names = [];
|
||||
|
||||
foreach ($values as $value) {
|
||||
if (is_string($value) && $value !== '') {
|
||||
$names[] = $value;
|
||||
}
|
||||
}
|
||||
|
||||
if ($names !== []) {
|
||||
$out[$key] = $names;
|
||||
}
|
||||
}
|
||||
|
||||
return $out;
|
||||
}
|
||||
|
||||
public function encode(): ?string
|
||||
{
|
||||
$payload = [
|
||||
|
||||
@ -15,7 +15,6 @@ final class SourceScope
|
||||
/** @var array<string, bool> */
|
||||
private array $containsCache = [];
|
||||
|
||||
|
||||
private const array TOP_LEVEL_NOISE = [
|
||||
'vendor',
|
||||
'node_modules',
|
||||
|
||||
Reference in New Issue
Block a user