Skip to content

Support component-based and Packwerk applications (#23)#47

Merged
JuanVqz merged 2 commits into
mainfrom
feature/packwerk-component-support
Jul 10, 2026
Merged

Support component-based and Packwerk applications (#23)#47
JuanVqz merged 2 commits into
mainfrom
feature/packwerk-component-support

Conversation

@etagwerker

Copy link
Copy Markdown
Member

Closes #23

Description

rails_stats already picked up files from packs/* and components/* when you ran rake stats at the root, but it merged everything into one table. There was no way to see how big each pack was, and (per #23) asking for a single pack with rake stats[packs/pack1] from inside a booted app quietly returned stats for the whole app instead.

This PR makes rails_stats understand large Rails monoliths built with Packwerk / packs-rails or component-based Rails.

What changes

When packs or components are detected, rake stats prints:

  • a == Core Application == section for everything in the main app
  • a == Pack: packs/pack1 == section for each pack/component
  • a == Total (all packs) == section aggregating the whole monolith

The same breakdown is available in JSON under a "packs" key. Plain Rails apps (no packs) get the exact same single-table output as before, so this is backwards compatible.

How packs are detected

A new RailsStats::PackFinder treats a directory as a pack when it either:

  • contains a package.yml file (the Packwerk marker), or
  • lives directly under a packs/ or components/ directory and holds any of app, lib, spec, or test.

Nested packs (packs/a/packs/b) are supported, and each file is attributed to its most specific pack. Packs that are self-contained gems/engines (they have a .gemspec) keep being counted by GemStatistics, so nothing is double-counted.

Bug fix

rake stats[packs/pack1] was ignoring its path argument when run inside a booted Rails app, because tasks.rb overwrote the given path with Rails.root unconditionally. It now only falls back to Rails.root when no path is given.

Testing

  • Added a test/fixtures/packwerk_app fixture with a core app, two packs (one Packwerk-marked, one convention-only), and a component.
  • Added tests for PackFinder, the grouped StatsCalculator#statistics_by_group, and the grouped console output.
  • Existing tests still pass unchanged (plain-app output is untouched).
14 runs, 90 assertions, 0 failures, 0 errors, 0 skips
  • Added an entry to CHANGELOG.md under the "main (unreleased)" heading.

I will abide by the code of conduct.

🤖 Generated with Claude Code

Add first-class support for large Rails monoliths organized into Packwerk
packs (packs-rails) or components (component-based Rails). When packs or
components are detected, `rake stats` now prints a breakdown per pack, a
`Core Application` section, and a `Total (all packs)` section. The same
breakdown is exposed in JSON under a `"packs"` key.

Packs are detected by `PackFinder`: a directory is a pack when it holds a
`package.yml` (Packwerk marker) or lives directly under `packs/`/`components/`
with an `app`/`lib`/`spec`/`test` folder. Nested packs are supported.

Also fixes the bug from #23 where an explicit path argument
(`rake stats[packs/pack1]`) was ignored when running inside a booted Rails
app because it was overwritten by `Rails.root`.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@codecov

codecov Bot commented Jul 10, 2026

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 95.78947% with 8 lines in your changes missing coverage. Please review.
✅ Project coverage is 86.25%. Comparing base (3a87be4) to head (f0c6276).
⚠️ Report is 26 commits behind head on main.

Files with missing lines Patch % Lines
lib/rails_stats/json_formatter.rb 22.22% 7 Missing ⚠️
lib/rails_stats/tasks.rb 0.00% 1 Missing ⚠️
Additional details and impacted files
@@            Coverage Diff             @@
##             main      #47      +/-   ##
==========================================
+ Coverage   83.20%   86.25%   +3.05%     
==========================================
  Files          19       23       +4     
  Lines         750      946     +196     
==========================================
+ Hits          624      816     +192     
- Misses        126      130       +4     

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Copilot AI left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds first-class support for Packwerk/packs-rails and component-based Rails monolith layouts by detecting packs/components and reporting rake stats output (console + JSON) broken down by “Core” plus per-pack groups, while keeping plain Rails apps’ output unchanged.

Changes:

  • Introduces RailsStats::PackFinder and adds grouped aggregation via StatsCalculator#statistics_by_group with pack attribution.
  • Updates console and JSON formatters to render per-pack sections and a grand total when packs/components are present.
  • Fixes rake stats[packs/pack1] inside a booted Rails app by only defaulting to Rails.root when no path argument is provided; adds fixtures + tests + changelog/docs updates.

Reviewed changes

Copilot reviewed 31 out of 31 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
lib/rails_stats/pack_finder.rb New pack/component detection used to enable grouped stats output.
lib/rails_stats/stats_calculator.rb Adds pack detection, grouping logic, and per-group total helpers.
lib/rails_stats/console_formatter.rb Prints per-pack sections + core + grand total when packs are detected.
lib/rails_stats/json_formatter.rb Adds a "packs" breakdown section in JSON output when packs are detected.
lib/rails_stats/stats_formatter.rb Plumbs statistics_by_group into formatters.
lib/rails_stats/tasks.rb Fixes path argument handling in booted Rails apps for rake stats[PATH].
lib/rails_stats/all.rb Loads the new pack_finder implementation.
lib/rails_stats/app_statistics.rb Exposes directory for grouping attribution.
lib/rails_stats/root_statistics.rb Exposes directory for grouping attribution.
lib/rails_stats/spec_statistics.rb Exposes directory for grouping attribution.
lib/rails_stats/test_statistics.rb Exposes directory for grouping attribution.
lib/rails_stats/cucumber_statistics.rb Exposes directory for grouping attribution.
lib/rails_stats/gem_statistics.rb Exposes directory for grouping attribution.
test/lib/rails_stats/pack_finder_test.rb Tests pack/component detection behavior and exclusions.
test/lib/rails_stats/stats_calculator_test.rb Tests grouping behavior, attribution, and totals consistency.
test/lib/rails_stats/console_formatter_test.rb Tests grouped console output sections for pack apps.
test/fixtures/packwerk_app/package.yml Fixture root Packwerk marker for pack detection tests.
test/fixtures/packwerk_app/app/models/core_model.rb Core-app fixture code for attribution tests.
test/fixtures/packwerk_app/app/controllers/core_controller.rb Core-app fixture code for attribution tests.
test/fixtures/packwerk_app/lib/core_lib.rb Core-app root lib fixture for core grouping.
test/fixtures/packwerk_app/packs/pack1/package.yml Pack fixture marker to validate Packwerk pack detection.
test/fixtures/packwerk_app/packs/pack1/app/models/pack1_model.rb Pack fixture code for per-pack attribution.
test/fixtures/packwerk_app/packs/pack1/app/controllers/pack1_controller.rb Pack fixture code for per-pack attribution.
test/fixtures/packwerk_app/packs/pack1/lib/pack1_lib.rb Pack fixture lib coverage for non-gem pack root scanning.
test/fixtures/packwerk_app/packs/pack1/spec/models/pack1_model_spec.rb Pack fixture spec for per-pack test attribution.
test/fixtures/packwerk_app/packs/pack2/app/models/pack2_model.rb Convention-only pack fixture code for detection/attribution.
test/fixtures/packwerk_app/packs/pack2/test/models/pack2_model_test.rb Convention-only pack fixture test for per-pack attribution.
test/fixtures/packwerk_app/components/billing/app/models/billing_model.rb Component fixture code for detection/attribution.
test/fixtures/packwerk_app/components/billing/lib/billing.rb Component fixture lib for per-component attribution.
README.md Documents new grouped output behavior and how pack detection works.
CHANGELOG.md Notes the new feature and the path-argument bugfix.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment on lines +39 to +42
def totals_for(group_statistics)
code = CodeStatisticsCalculator.new
tests = CodeStatisticsCalculator.new
grand = CodeStatisticsCalculator.new
Comment on lines +35 to +48
Dir.glob(File.join(root, "**", "package.yml")).each do |marker_path|
next if marker_path =~ IGNORED_PATH

dir = File.absolute_path(File.dirname(marker_path))
next if dir == root

packs[dir] = true
end
end

def collect_convention_packs(root, packs)
PACK_CONTAINERS.each do |container|
Dir.glob(File.join(root, "**", container, "*")).each do |path|
next if path =~ IGNORED_PATH
The recursive file walker (Util.calculate_directory_statistics) descended
into node_modules and other vendored directories. This had two effects on a
packs/component monolith, where each pack can carry its own
app/webpack/node_modules:

  * it crashed the run: a directory named like a source file (e.g.
    node_modules/popper.js) matched the file pattern and was opened as a
    file, raising Errno::EISDIR.
  * when it did complete, it inflated every number by millions of vendored
    files.

Fix it at the root, in the single walker: prune traversal into
node_modules/vendor/tmp/log/.git and never treat a directory as a countable
file. The ignored-name list now lives in one place (RailsStats::IGNORED_DIRS);
PackFinder builds its path regex from it and Util matches it by basename.

Measured on a large packs-based application (17 packs), same command,
before vs. after this change:

  | Metric              |            Before |     After |
  |---------------------|-------------------|-----------|
  | Completes run       | no (Errno::EISDIR)|       yes |
  | Total files         |         4,903,892 |     5,033 |
  | Total LOC           |         3,395,731 |   418,681 |
  | Webpacks row: files |            52,075 |       696 |
  | Webpacks row: lines |         4,447,768 |    54,495 |
  | Code:Test ratio     |             1:0.1 |     1:1.1 |
@JuanVqz

JuanVqz commented Jul 10, 2026

Copy link
Copy Markdown
Member

@etagwerker Ran this branch against a large packs-based monolith (17 packs) to QA the per-pack breakdown. The grouping works well, but the run first crashed and then produced wildly inflated numbers because the file walker descended into each pack's app/webpack/node_modules. The fix (skip node_modules/vendor/tmp/log/.git when counting, and never open a directory as a file) resolves both.

Same command, before vs. after the fix:

Metric Before After
Completes run no (Errno::EISDIR) yes
Total files 4,903,892 5,033
Total LOC 3,395,731 418,681
Webpacks row: files 52,075 696
Webpacks row: lines 4,447,768 54,495
Code:Test ratio 1:0.1 1:1.1

@JuanVqz JuanVqz merged commit 1a5f5c7 into main Jul 10, 2026
8 checks passed
@JuanVqz JuanVqz deleted the feature/packwerk-component-support branch July 10, 2026 19:13
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Calculating stats for a packwere pack inside the app shows stats for the whole app unless I run it from outside the app

3 participants