Skip to content

Publish only a whitelist of files in the gem - #226

Merged
Fivell merged 5 commits into
masterfrom
chore/exclude-specs-from-gem
Sep 30, 2026
Merged

Fivell merged 5 commits into
masterfrom
chore/exclude-specs-from-gem

Conversation

@Fivell

@Fivell Fivell commented Sep 30, 2026 •

Copy link
Copy Markdown
Member

What

files is now an explicit whitelist, instead of publishing everything tracked minus whatever someone remembered to exclude.

gem.files = `git ls-files -z -- lib app vendor config exe bin README.md LICENSE`.split("\x0")

A reject list only removes what someone named. That is how the test suite, CI config ended up in the published gem — each needed its own pattern, and none was added until an audit went looking. A whitelist inverts the default: a new directory does not reach consumers until it is listed.

Every root Rails::Engine loads from is listed, present in this repo or not. A whitelist fails quietly — git ls-files -- config against a tree with no config/ exits 0 and prints nothing — so the day someone adds config/initializers/foo.rb the gem would install, boot, and never run it. Naming the roots up front costs nothing (git ls-files on a missing path is a no-op) and removes that trapdoor.

Selected through git ls-files rather than Dir[...] so the artifact stays tracked-only — an untracked or generated file under lib/ cannot leak into a release — and so files carries no directory entries.

Verified

$ gem build active_admin_import.gemspec
65 files / 32K   ->   25 files / 20K

All 40 files that leave the package:

.github/workflows/test.yml
.gitignore
.rubocop.yml
Gemfile
Rakefile
active_admin_import.gemspec
spec/fixtures/files/author.csv
spec/fixtures/files/author_broken_header.csv
spec/fixtures/files/author_invalid.csv
spec/fixtures/files/author_invalid_format.txt
spec/fixtures/files/authors.csv
spec/fixtures/files/authors_blank_header_end.csv
spec/fixtures/files/authors_blank_header_middle.csv
spec/fixtures/files/authors_bom.csv
spec/fixtures/files/authors_empty_header.csv
spec/fixtures/files/authors_invalid_db.csv
spec/fixtures/files/authors_invalid_model.csv
spec/fixtures/files/authors_many.csv
spec/fixtures/files/authors_no_headers.csv
spec/fixtures/files/authors_values_exceeded_headers.csv
spec/fixtures/files/authors_win1251_win_endline.csv
spec/fixtures/files/authors_with_ids.csv
spec/fixtures/files/authors_with_semicolons.csv
spec/fixtures/files/authors_with_tabs.tsv
spec/fixtures/files/empty.csv
spec/fixtures/files/only_headers.csv
spec/fixtures/files/post_comments.csv
spec/fixtures/files/posts.csv
spec/fixtures/files/posts_for_author.csv
spec/fixtures/files/posts_for_author_no_headers.csv
spec/import_result_spec.rb
spec/import_spec.rb
spec/model_spec.rb
spec/spec_helper.rb
spec/support/active_model_lint.rb
spec/support/admin.rb
spec/support/import_form_selectors.rb
spec/support/rails_template.rb
spec/support/test_app_paths.rb
tasks/test.rake

Nothing is added. The 23 files under app/, config/, lib/ — the entire runtime payload — are unchanged:

app/views/admin/import.html.erb
config/locales/de.yml
config/locales/en.yml
config/locales/es.yml
config/locales/fr.yml
config/locales/it.yml
config/locales/ja.yml
config/locales/ko.yml
config/locales/pt-BR.yml
config/locales/ru.yml
config/locales/tr.yml
config/locales/uk.yml
config/locales/zh-CN.yml
lib/active_admin_import.rb
lib/active_admin_import/authorization.rb
lib/active_admin_import/dsl.rb
lib/active_admin_import/engine.rb
lib/active_admin_import/exception.rb
lib/active_admin_import/import_result.rb
lib/active_admin_import/importer.rb
lib/active_admin_import/model.rb
lib/active_admin_import/options.rb
lib/active_admin_import/version.rb

`git ls-files` with no filter shipped the whole test suite: 29 of the
gem's 61 packaged files lived under spec/, including 20 CSV/TSV
fixtures and the 25 KB spec/import_spec.rb. Consumers download all of
it and use none of it.

Also switches to -z/\x0 splitting. `$OUTPUT_RECORD_SEPARATOR` only
worked by accident — `English` is never required, so the global is nil
and `split(nil)` falls back to whitespace splitting, which breaks on
any tracked path containing a space.

Packaged files: 61 -> 31.
The reject list only removes directories someone remembered to name.
That is how spec/, .github/, screen/ and img/ got published in the
first place — each needed a new pattern, and none was added until an
audit went looking.

A whitelist inverts the default: a new directory in the repo does not
reach consumers until it is listed. Same shape the sibling gems
activeadmin-oidc and credit_card_validations already use.

Drops the remaining dev-only files the reject form kept:

  .gitignore .rubocop.yml active_admin_import.gemspec Gemfile Rakefile tasks/test.rake

Packaged: 31 -> 25 files. The runtime payload — everything
under lib/, app/, vendor/, config/ and exe/ — is byte-identical to
before, verified by diffing the built .gem both ways.
@Fivell Fivell self-assigned this Sep 30, 2026
@Fivell
Fivell requested a balanced review from Copilot September 30, 2026 12:25

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Copilot review overview

🟡 Changes recommended

The new glob can unintentionally package untracked or generated local files.

Review effort: Balanced
Findings: 1 Medium severity

Open (1)
What changed in this PR

Limits packaged gem contents to runtime files, excluding specs and CI configuration.

Changes:

  • Whitelists runtime directories, README, and license.
  • Removes tests and repository metadata from gem packaging.
File Description
active_admin_import.gemspec Defines the package file whitelist.

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread active_admin_import.gemspec Outdated
`Dir[...]` globs the working tree, so any untracked or generated file
under lib/, app/ or vendor/ would be published in a release — the build
artifact depended on the releaser's local checkout. The reject form it
replaced was tracked-only; this restores that property while keeping
the whitelist.

`git ls-files -- <paths>` also returns files only, where `Dir["**/*"]`
returns directory entries too, so `files` no longer carries entries
RubyGems just ignores.

Built .gem is byte-for-byte the same file list as the Dir[] version.
@Fivell Fivell changed the title Keep specs and CI config out of the packaged gem Publish only a whitelist of files in the gem Sep 30, 2026
`executables` greps `files` for `^bin/`, but `bin` was not in the
whitelist, so that grep could never match. Adding a `bin/foo` later
would have published a gem with no executables and no build error to
say so — exactly the failure the whitelist exists to prevent, inverted.

No repo file changes today: none of these gems tracks a bin/, and the
built .gem is identical.
A whitelist fails quietly: `git ls-files -- config` against a tree with
no config/ exits 0 and prints nothing, so the day someone adds
`config/initializers/foo.rb` the gem installs, boots, and the
initializer never runs. Nothing in `gem build` warns.

That is not hypothetical for this family of gems —
active_admin_datetimepicker's Ransack predicates live in exactly such
an initializer, and its filters return no results without them.

So list every root Rails::Engine loads from (lib app vendor config exe
bin) in all of them, present or not, instead of only the ones that
happen to exist today. No package changes: the built .gem is identical
in every gem.
@Fivell
Fivell merged commit 43c467b into master Sep 30, 2026
38 checks passed
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.

2 participants