Skip to content

ci: add the gem's CI workflow + the missing generator specs#25

Merged
mhenrixon merged 3 commits into
mainfrom
issue-23-ci-and-generator-specs
Jul 3, 2026
Merged

ci: add the gem's CI workflow + the missing generator specs#25
mhenrixon merged 3 commits into
mainfrom
issue-23-ci-and-generator-specs

Conversation

@mhenrixon

Copy link
Copy Markdown
Owner

Closes #23. Part of #8 (Phase 5) — the CI gate every other issue in the epic merges through.

What this does

The gem repo had no CI: bundle exec rake (rspec + rubocop) was trust-based on every PR, and CLAUDE.md's claim that "Generator specs cover docs_kit:install output" was false — spec/generators/ did not exist. This wires real CI + real generator specs and makes the docs true.

CI — .github/workflows/ci.yml

  • Runs on push to main and all PRs.
  • Matrix: Ruby 3.2 / 3.3 / 3.4 (matches the gemspec's >= 3.2).
  • ruby/setup-ruby with bundler-cache: true, then bundle exec rake.
  • Concurrency-cancel superseded runs on the same ref.
  • One job — the generator specs invoke the install generator via plain Thor against a tmp app skeleton (no Rails boot), so they need no heavier harness than the rest of the suite.

Generator specs — spec/generators/install_generator_spec.rb

14 examples. Each builds a minimal fake-app skeleton (routes.rb, application_controller.rb, controllers/index.js, assets.rb) in a throwaway destination root, runs the generator, and asserts:

  • the full file manifest (initializers, registry model, controllers, pages, CSS build, keeps);
  • c.brand / c.title_suffix substituted from the humanized app name;
  • the theme list in the initializer matches the Tailwind @plugin block;
  • the docs + root route injection;
  • include DocsKit::Controller injected into ApplicationController (and skipped when absent);
  • the builds asset path appended to assets.rb;
  • the package.json stub scripts (and that an existing package.json with build:css is left untouched);
  • the docs_kit/controllers Stimulus registration (and skipped when there's no index.js);
  • bin/build-css created executable (chmod 0755).

These assert current behavior. I verified the generator is largely idempotent (Thor's route / inject_into_file guard against duplicate lines; several steps say_status(:skip) on an existing target) — the issue's assumption that add_routes blind-appends is stale, so there's no idempotency bug to file. create_initializer re-renders on every run, which is fine.

Coverage gate

SimpleCov in spec/spec_helper.rb with minimum_coverage 80 — the 80% floor documented in CLAUDE.md is now enforced (the suite fails below it, locally and in CI). Current line coverage: 86.76%.

Incidental fixes (CI-blocking, pre-existing)

bundle exec rubocop already crashed on clean main (verified by stashing my changes) — it descended into the tracked docs/ dogfood site, whose .rubocop.yml inherits rubocop-rails-omakase (not in the gem's bundle), and into agent scratch worktrees. Without fixing this, CI's rake can never go green. Fixes:

  • Excluded docs/, .claude/, coverage/, tmp/ in .rubocop.yml (the docs/ site lints itself with its own bundle).
  • Guarded the daisyui path override in the Gemfile (if File.directory?(...)) so CI / a fresh clone without the sibling checkout resolves daisyui from rubygems via the gemspec dependency.
  • Ignored .claude/worktrees/.

Docs

  • CLAUDE.md + .claude/rules/testing.md: the generator-specs claim is now true; documented the SimpleCov enforcement and the CI matrix.
  • README: added a CI badge.

Test plan

  • bundle exec rake green locally — 75 examples, 0 failures, 86.76% line coverage, rubocop clean (39 files, 0 offenses).
  • SimpleCov gate proven: flipping a spec assertion goes RED; coverage below 80 fails the suite.
  • This PR's CI check runs green on all three Rubies (watch the Actions tab).
  • Manual, post-merge: make CI a required check on main in repo settings (Settings → Branches → branch protection). GitHub Actions can't self-configure branch protection.

Out of scope

  • CI for the consumer sites (their own repos).
  • The deploy workflows (untouched).

mhenrixon added 3 commits July 3, 2026 08:00
## Summary

The gem repo had no CI: `bundle exec rake` (rspec + rubocop) was trust-based on
every PR, and CLAUDE.md's claim that "Generator specs cover docs_kit:install
output" was false — spec/generators/ did not exist. This wires real CI + real
generator specs and makes the docs true.

- `.github/workflows/ci.yml`: push to main + all PRs; matrix Ruby 3.2/3.3/3.4;
  ruby/setup-ruby with bundler-cache; `bundle exec rake`; concurrency-cancel on
  the same ref. One job — the generator specs run plain Thor against a tmp app
  skeleton (no Rails boot), so no heavier harness is needed.
- `spec/generators/install_generator_spec.rb`: runs the install generator against
  a throwaway destination root (a minimal fake app skeleton) and asserts the file
  manifest + key contents (brand substitution, route injection, the
  `include DocsKit::Controller` inject, package.json scripts, the docs_kit
  Stimulus registration, chmod 0755 on bin/build-css, theme list ↔ CSS sync).
- SimpleCov in spec_helper with `minimum_coverage 80` — the 80% floor from
  CLAUDE.md is now enforced (suite fails below it, locally and in CI). Line
  coverage: 86.76%.

## Incidental fixes (CI-blocking, pre-existing)

- `bundle exec rubocop` crashed on clean main: it descended into the tracked
  `docs/` dogfood site (inherits rubocop-rails-omakase, not in the gem's bundle)
  and into agent scratch worktrees. Excluded `docs/`, `.claude/`, `coverage/`,
  `tmp/` in .rubocop.yml so `rake` (and CI) runs.
- Guarded the `daisyui` path override in the Gemfile (`if File.directory?`) so a
  fresh clone / CI without the sibling checkout resolves daisyui from rubygems.
- Ignored `.claude/worktrees/`.

## Test Coverage

- 14 generator examples asserting the install manifest + key file contents
  against current behavior (largely idempotent; create_initializer re-renders).
- SimpleCov gate proven: flipping an assertion goes RED; coverage below 80 fails.

## Verification

- [x] bundle exec rake passes (75 examples, 0 failures; 86.76% coverage; rubocop
      clean — 39 files, 0 offenses)

Refs #23
DocsUI::Shell and Sidebar `include DaisyUI` (the daisyUI Phlex kit). The gem name
is `daisyui`; the require path is `daisy_ui` — a consuming site loads it via
`gem "daisyui", require: "daisy_ui"`. docs_kit itself never requires it (the host
app does).

The suite requires only `docs_kit`, so nothing loaded the daisyUI kit. Locally it
passed by accident — the sibling `../daisyui` checkout's loader happened to be
active. On CI (published daisyui 1.2.0, no sibling) `include DaisyUI` raised
`NameError: uninitialized constant DocsUI::Shell::DaisyUI` and every
component spec errored on load.

Fix at the earliest point: `spec_helper.rb` requires `daisy_ui` before `docs_kit`,
exactly as a real consuming site does. Verified by hiding the sibling checkout so
Bundler resolves daisyui 1.2.0 from rubygems (CI's exact state): 75 examples,
0 failures, 86.76% coverage, rubocop clean.

Refs #23
RuboCop discovers and loads .rubocop.yml files across the whole tree, including
docs/.rubocop.yml — the dogfood site's config, which `inherit_gem`s
rubocop-rails-omakase (and, transitively, panolint). Those gems live in the docs/
bundle, not the gem's, so on CI the inheritance can't resolve and RuboCop crashes
during config loading — before linting a single file.

An AllCops Exclude of docs/ stops linting those files but NOT loading their config
as directory inheritance. The fix is to pass RuboCop explicit paths so it never
discovers docs/.rubocop.yml: scope the rake task to app/ lib/ spec/ + the root
Ruby files. Verified with --debug that only the root .rubocop.yml is loaded, and
that a full `rake` under CI conditions (published daisyui, no sibling checkout) is
green: 75 examples 0 failures, 86.76% coverage, 37 files rubocop-clean.

docs/ lints itself with its own bundle. The AllCops Exclude stays as defense for
anyone running `rubocop` bare.

Refs #23
@mhenrixon

Copy link
Copy Markdown
Owner Author

CI is green on 3.2 / 3.3 / 3.4 ✅

Two additional CI-blocking bugs surfaced only once the workflow ran against the published daisyui 1.2.0 (the local suite passed by relying on the sibling ../daisyui checkout). Both are fixed in follow-up commits:

  1. fix(spec): require daisy_ui — the component suite include DaisyUI but nothing loaded the daisyUI kit (docs-kit expects the host app's require: "daisy_ui"). On CI: NameError: uninitialized constant DocsUI::Shell::DaisyUI. Fixed by requiring daisy_ui in spec_helper.rb, exactly as a consuming site does.

  2. ci: scope the rubocop rake task — RuboCop loaded docs/.rubocop.yml (the dogfood site's config, which inherit_gems rubocop-rails-omakasepanolint, absent from the gem's bundle) and crashed during config resolution. An AllCops Exclude stops linting those files but not loading their config as directory inheritance, so the rake task now passes explicit paths (app lib spec + root files). Verified with --debug that only the root .rubocop.yml is read.

Both were reproduced locally by hiding the sibling checkout so Bundler resolves daisyui 1.2.0 from rubygems (CI's exact state) — rake is green there too.

@mhenrixon mhenrixon self-assigned this Jul 3, 2026
@mhenrixon mhenrixon merged commit 70165ef into main Jul 3, 2026
3 checks passed
@mhenrixon mhenrixon deleted the issue-23-ci-and-generator-specs branch July 4, 2026 14:42
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.

ci: add the gem's CI workflow + the missing generator specs

1 participant