Skip to content
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
2 changes: 1 addition & 1 deletion .github/wiki
Submodule wiki updated from 13a815 to 005922
9 changes: 9 additions & 0 deletions .github/workflows/wiki-preview.yml
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ jobs:
name: Update Wiki Preview
runs-on: ubuntu-latest
permissions:
actions: write
contents: write
pull-requests: read

Expand Down Expand Up @@ -110,6 +111,13 @@ jobs:
pull: "--rebase --autostash"
push: true

- name: Dispatch tests for wiki pointer commit
if: steps.submodule_status.outputs.changed == 'true'
env:
GH_TOKEN: ${{ github.token }}
HEAD_REF: ${{ github.event.pull_request.head.ref }}
run: gh workflow run tests.yml --ref "${HEAD_REF}" -f max-outdated=-1

- uses: ./.dev-tools-actions/.github/actions/summary/write
with:
markdown: |
Expand All @@ -118,3 +126,4 @@ jobs:
- Preview branch: `${{ env.WIKI_PREVIEW_BRANCH }}`
- Submodule pointer changed: `${{ steps.submodule_status.outputs.changed }}`
- Parent repository pointer commit result: `${{ steps.submodule_status.outputs.changed == 'true' && 'updated' || 'unchanged' }}`
- Tests dispatch result: `${{ steps.submodule_status.outputs.changed == 'true' && 'requested' || 'not needed' }}`
1 change: 1 addition & 0 deletions .github/workflows/wiki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@ concurrency:
jobs:
preview:
permissions:
actions: write
contents: write
pull-requests: read
uses: ./.github/workflows/wiki-preview.yml
Expand Down
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0
### Fixed

- Keep required PHPUnit matrix checks reporting after workflow-managed `.github/wiki` pointer commits by running the pull-request test workflow without top-level path filters and aligning the packaged consumer test wrapper (#230)
- Ignore intentional Composer Dependency Analyser shadow dependency findings by default while adding `dependencies --show-shadow-dependencies` for audits (#233)
- Dispatch the required test workflow after wiki preview automation updates a pull-request `.github/wiki` pointer, avoiding permanently pending required checks on bot-authored pointer commits (#230)

## [1.21.0] - 2026-04-24

Expand Down
17 changes: 12 additions & 5 deletions docs/advanced/branch-protection-and-bot-commits.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,9 +99,15 @@ updates on PR branches while keeping ``main`` protected.
Required test checks must still report for workflow-managed pointer commits.
The tests workflow therefore triggers on every pull request update without
top-level path filters. This ensures GitHub always creates the required
``Run Tests`` matrix checks for the latest pull request head, including bot
commits that only refresh ``.github/wiki``. Test workflow concurrency cancels
older in-progress runs for the same pull request so the newest commit owns the
``Run Tests`` matrix checks for ordinary pull request updates.

Workflow-managed ``.github/wiki`` pointer commits need one extra step. GitHub
does not start another ``pull_request`` or ``push`` workflow run for commits
pushed with the built-in workflow token. After the wiki preview workflow commits
a parent-repository pointer update, it explicitly dispatches ``tests.yml`` for
the pull request head branch so the newest bot-authored commit receives the
required ``Run Tests`` matrix checks. Test workflow concurrency cancels older
in-progress runs for the same pull request so the newest commit owns the
required check contexts.

At a high level, the workflows need permission to read repository contents,
Expand All @@ -128,8 +134,9 @@ distinguish open pull requests from closed or merged ones before deleting

``wiki.yml`` is now preview-only, so its called workflow keeps
``contents: write`` for wiki preview commits and parent-repository submodule
pointer updates, while retaining ``pull-requests: read`` to inspect pull
request metadata safely.
pointer updates, ``actions: write`` to dispatch ``tests.yml`` after bot-authored
pointer commits, and ``pull-requests: read`` to inspect pull request metadata
safely.

``wiki-maintenance-entry.yml`` and ``wiki-maintenance.yml`` keep
``contents: write`` for wiki publication and cleanup tasks, and
Expand Down
18 changes: 18 additions & 0 deletions docs/commands/dependencies.rst
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@ Options
Asks ``composer-dependency-analyser`` to dump usages for the given package
or wildcard pattern and enables ``--show-all-usages`` automatically.

``--show-shadow-dependencies`` (optional)
Reports shadow dependencies instead of applying the Fast Forward default
ignore for intentional dependency groups.

By default, DevTools hides ``SHADOW_DEPENDENCY`` findings because Fast
Forward packages may intentionally require ecosystem bundles, meta packages,
or convenience packages that install related dependencies for consumers.
Use this flag when auditing whether a package has accidental shadow
dependencies that should be removed or documented more precisely.

``--json``
Emit a structured machine-readable payload instead of the normal terminal
output.
Expand Down Expand Up @@ -95,6 +105,12 @@ Dump all matched usages for one package:

composer dependencies --dump-usage=symfony/console

Audit shadow dependencies:

.. code-block:: bash

composer dependencies --show-shadow-dependencies

Apply the upgrade workflow and then analyze dependencies:

.. code-block:: bash
Expand Down Expand Up @@ -135,6 +151,8 @@ Behavior
consumer repositories can extend the baseline instead of copying it whole
- ``--dump-usages <package>`` and ``--show-all-usages`` when ``--dump-usage``
is passed to the DevTools command
- the ``FAST_FORWARD_DEV_TOOLS_SHOW_SHADOW_DEPENDENCIES`` process environment
flag, which is enabled when ``--show-shadow-dependencies`` is passed
- ``jack breakpoint`` maps ``--max-outdated`` to Jack's ``--limit`` option.
- ``--max-outdated=-1`` keeps ``jack breakpoint`` in the workflow for reporting,
but its failure is ignored so only missing or unused dependency findings fail
Expand Down
7 changes: 7 additions & 0 deletions docs/configuration/overriding-defaults.rst
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,13 @@ consumers can extend the default configuration using the
This approach keeps the Fast Forward baseline while letting consumer
repositories add project-specific ignores or scan rules.

The baseline ignores ``SHADOW_DEPENDENCY`` findings by default because Fast
Forward packages may intentionally require dependency groups, ecosystem bundles,
or meta packages that install related dependencies for consumers. Run
``composer dependencies --show-shadow-dependencies`` when you want to audit
those findings and decide whether a package should keep, document, or remove a
direct dependency.

What Is Not Overwritten Automatically
--------------------------------------

Expand Down
5 changes: 5 additions & 0 deletions docs/running/specialized-commands.rst
Original file line number Diff line number Diff line change
Expand Up @@ -77,6 +77,7 @@ Analyzes missing, unused, misplaced, and outdated Composer dependencies.
composer dependencies --max-outdated=-1
composer dependencies --dev
composer dependencies --dump-usage=symfony/console
composer dependencies --show-shadow-dependencies
composer dependencies --upgrade --dev

Important details:
Expand All @@ -88,6 +89,10 @@ Important details:
override locally;
- ``--dump-usage=<package>`` forwards to
``composer-dependency-analyser --dump-usages <package> --show-all-usages``;
- ``--show-shadow-dependencies`` keeps shadow dependency findings visible for
audits; without it, DevTools hides intentional Fast Forward dependency-group
shadows so CI does not fail on ecosystem or meta packages that deliberately
install related dependencies for consumers;
- it uses ``jack breakpoint --limit=<max-outdated>`` to fail when too many
outdated dependencies accumulate;
- ``--max-outdated=-1`` keeps the Jack outdated report in the output but
Expand Down
2 changes: 2 additions & 0 deletions resources/github-actions/wiki.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ on:
types: [opened, synchronize, reopened]

permissions:
actions: write
contents: write
pull-requests: read

Expand All @@ -15,6 +16,7 @@ concurrency:
jobs:
preview:
permissions:
actions: write
contents: write
pull-requests: read
# Pull-request wiki previews live here. Publication and preview cleanup are
Expand Down
35 changes: 34 additions & 1 deletion src/Config/ComposerDependencyAnalyserConfig.php
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@
*/
final class ComposerDependencyAnalyserConfig
{
public const string ENV_SHOW_SHADOW_DEPENDENCIES = 'FAST_FORWARD_DEV_TOOLS_SHOW_SHADOW_DEPENDENCIES';

/**
* Dependencies that are only required by the packaged DevTools distribution itself.
*
Expand Down Expand Up @@ -90,6 +92,10 @@ public static function configure(?callable $customize = null): Configuration
{
$configuration = new Configuration();

if (! self::shouldShowShadowDependencies()) {
self::applyIgnoresShadowDependencies($configuration);
}

if (DevToolsPathResolver::isRepositoryCheckout()) {
self::applyPackagedRepositoryIgnores($configuration);
}
Expand All @@ -101,12 +107,39 @@ public static function configure(?callable $customize = null): Configuration
return $configuration;
}

/**
* The default configuration ignores shadow dependencies because Fast
* Forward packages MAY intentionally require dependency groups. For example,
* ecosystem or meta packages can require related PSR or framework packages
* so consumers do not need to install every package one by one.
*
* @param Configuration $configuration the analyser configuration to customize
*
* @return Configuration the modified configuration with shadow dependencies ignored
*/
public static function applyIgnoresShadowDependencies(Configuration $configuration): Configuration
{
$configuration->ignoreErrors([ErrorType::SHADOW_DEPENDENCY]);

return $configuration;
}

/**
* Determines whether shadow dependency reports SHOULD remain visible.
*
* @return bool
*/
public static function shouldShowShadowDependencies(): bool
{
return '1' === getenv(self::ENV_SHOW_SHADOW_DEPENDENCIES);
}

/**
* Applies the ignores required only by the packaged DevTools repository.
*
* @param Configuration $configuration the analyser configuration to customize
*
* @return void
* @return Configuration the modified configuration with packaged repository ignores applied
*/
public static function applyPackagedRepositoryIgnores(Configuration $configuration): Configuration
{
Expand Down
14 changes: 13 additions & 1 deletion src/Console/Command/DependenciesCommand.php
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@
use FastForward\DevTools\Console\Command\Traits\LogsCommandResults;
use Composer\Command\BaseCommand;
use FastForward\DevTools\Console\Input\HasJsonOption;
use FastForward\DevTools\Config\ComposerDependencyAnalyserConfig;
use FastForward\DevTools\Process\ProcessBuilderInterface;
use FastForward\DevTools\Process\ProcessQueueInterface;
use InvalidArgumentException;
Expand Down Expand Up @@ -101,6 +102,11 @@ protected function configure(): void
name: 'dump-usage',
mode: InputOption::VALUE_REQUIRED,
description: 'Dump usages for the given package pattern and show all matched usages.',
)
->addOption(
name: 'show-shadow-dependencies',
mode: InputOption::VALUE_NONE,
description: 'Report shadow dependencies instead of applying Fast Forward intentional-shadow ignores.',
);
}

Expand Down Expand Up @@ -176,7 +182,13 @@ private function getComposerDependencyAnalyserCommand(InputInterface $input): Pr
->withArgument('--show-all-usages');
}

return $processBuilder->build('vendor/bin/composer-dependency-analyser');
$showShadowDependencies = (bool) $input->getOption('show-shadow-dependencies');
$process = $processBuilder->build('vendor/bin/composer-dependency-analyser');
$process->setEnv([
ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES => $showShadowDependencies ? '1' : '0',
]);

return $process;
}

/**
Expand Down
74 changes: 74 additions & 0 deletions tests/Config/ComposerDependencyAnalyserConfigTest.php
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,8 @@
use ShipMonk\ComposerDependencyAnalyser\Config\Configuration;
use ShipMonk\ComposerDependencyAnalyser\Config\ErrorType;

use function Safe\putenv;

#[CoversClass(ComposerDependencyAnalyserConfig::class)]
#[UsesClass(DevToolsPathResolver::class)]
final class ComposerDependencyAnalyserConfigTest extends TestCase
Expand All @@ -43,6 +45,48 @@ public function configureWillReturnConfiguration(): void
self::assertInstanceOf(Configuration::class, $configuration);
}

/**
* @return void
*/
#[Test]
public function configureWillIgnoreShadowDependenciesByDefault(): void
{
$originalValue = getenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES);

try {
putenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES);
$configuration = ComposerDependencyAnalyserConfig::configure();

self::assertTrue(
$configuration->getIgnoreList()
->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, null, 'vendor/shadow-package')
);
} finally {
self::restoreShadowDependenciesEnvironment($originalValue);
}
}

/**
* @return void
*/
#[Test]
public function configureWillKeepShadowDependenciesVisibleWhenRequested(): void
{
$originalValue = getenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES);

try {
putenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES . '=1');
$configuration = ComposerDependencyAnalyserConfig::configure();

self::assertFalse(
$configuration->getIgnoreList()
->shouldIgnoreError(ErrorType::SHADOW_DEPENDENCY, null, 'vendor/shadow-package')
);
} finally {
self::restoreShadowDependenciesEnvironment($originalValue);
}
}

/**
* @return void
*/
Expand Down Expand Up @@ -102,4 +146,34 @@ public function applyPackagedRepositoryIgnoresWillReturnTheSameConfigurationInst
ComposerDependencyAnalyserConfig::applyPackagedRepositoryIgnores($configuration)
);
}

/**
* @return void
*/
#[Test]
public function applyIgnoresShadowDependenciesWillReturnTheSameConfigurationInstance(): void
{
$configuration = new Configuration();

self::assertSame(
$configuration,
ComposerDependencyAnalyserConfig::applyIgnoresShadowDependencies($configuration)
);
}

/**
* @param false|string $value
*
* @return void
*/
private static function restoreShadowDependenciesEnvironment(false|string $value): void
{
if (false === $value) {
putenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES);

return;
}

putenv(ComposerDependencyAnalyserConfig::ENV_SHOW_SHADOW_DEPENDENCIES . '=' . $value);
}
}
Loading
Loading