feat(generator): version-aware migrations between docs-kit releases in --sync - #56
Merged
Merged
Conversation
…n --sync ## Summary `docs_kit:install --sync` was version-agnostic: it diffed a site against the current gem template with no notion of which docs-kit version the site was last synced from, so it could not apply *ordered* release-to-release migrations. This adds the mechanism (issue #41): - **Version stamp** — every install and `--sync` writes an inert `# docs-kit synced: vX.Y.Z` comment on the first line of the config initializer (the one file every site has). Injected into an existing, never-clobbered initializer; updated in place; idempotent. - **Migration + MigrationRegistry** — a Migration is an ordered, versioned transform (`to` = the release that introduced it) whose block returns the warn-only messages it couldn't safely automate. The registry selects the migrations in the half-open range `(last_synced, gem_version]` and runs them ascending, collecting warnings. - **--sync wiring** — reads the stamp BEFORE restamping, runs the applicable migrations, prints a "migration steps to apply by hand" checklist for anything warn-only, then restamps to the current version. Full (non-sync) installs don't migrate — they're a fresh scaffold, not an upgrade. The registry ships EMPTY at 1.0.x: the mechanism is the deliverable, and the first concrete `1.x → 1.y` transform is a one-line `Migration.new(...)` addition. ## Key invariant (surfaced by an end-to-end drive) `applicable` caps at the installed gem version (`upto:`, default `DocsKit::VERSION`). A migration targeting a version above the gem can't apply — the site can't have that release — and, without the cap, would re-fire on every sync after the site restamps to the gem version. The `upto` bound makes `--sync` idempotent. ## Backwards compatibility - A pre-feature site (un-stamped initializer) is treated as the earliest version (0.0.0) → every migration applies. Its config body is preserved byte-for-byte; only the inert stamp comment is prepended. - New sites via `docs-kit new` are stamped automatically (the template runs `docs_kit:install`). ## Test Coverage - spec/generators/migration_spec.rb — Migration value object (version coercion, block invocation, nil→[] warnings, natural version ordering). - spec/generators/migration_registry_spec.rb — applicable() gap logic (exclusive from, ascending, unknown/ahead sites, the upto ceiling) and migrate!() ordering + warning collection; .default ships empty. - spec/generators/install_generator_spec.rb — stamping (install + inject into existing + update stale + idempotent) and the --sync migration wiring (runs for a stamped/pre-feature site, prints warnings, no-op at current version, not on a full install). ## Verification - [x] bundle exec rspec passes (780 examples, 94.82% line coverage) - [x] bundle exec rubocop passes (no offenses) - [x] end-to-end drive of the real generator: stamp on install, gap-detected migration on sync, restamp, idempotent second sync Refs #41 Claude-Session: https://claude.ai/code/session_01FPQb6z3YwcKRMbvoJhdxnX
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Closes #41.
docs_kit:install --syncwas deliberately version-agnostic (the right v1 call): it reconciled a site against the current gem template with no notion of which docs-kit version the site was last synced from. Now that the gem is post-1.0, this adds the mechanism to apply the ordered migration steps between a site's last-synced version and the current one.What changed
--syncwrites an inert# docs-kit synced: vX.Y.Zcomment on the first line of the config initializer — the one file every site has. Injected into an existing (never-clobbered) initializer, updated in place, idempotent.Migrationto(the release it belongs to), a description, and a block run with(root, generator)that returns the warn-only messages it couldn't safely automate (nil→[]).MigrationRegistry(last_synced, gem_version], runs them ascending, collects warnings. Ships empty at 1.0.x — the mechanism is the deliverable.--syncwiringmigration steps to apply by hand:checklist for anything warn-only, then restamps. Full (non-sync) installs don't migrate — a fresh scaffold isn't an upgrade.The first concrete
1.x → 1.ymigration is now a one-lineMigration.new(to: "1.x.0", description: "...") { ... }addition toMigrationRegistry::MIGRATIONS.Key invariant (surfaced by an end-to-end drive, not the mocked tests)
applicablecaps at the installed gem version (upto:, defaultDocsKit::VERSION). A migration targeting a version above the gem can't legitimately apply — a site can't have "arrived" at a release it doesn't have — and, without the cap, would re-fire on every sync after the site restamps to the gem version. Theuptobound is what makes--syncidempotent. This bug only appeared when I drove the real generator on disk; it's now covered by both a unit spec and an end-to-end assertion.Backwards compatibility
0.0.0) → every migration applies. Its config body is preserved byte-for-byte; only the inert stamp comment is prepended.docs-kit neware stamped automatically — the new-site template runsdocs_kit:install, so no separate wiring is needed.lib/generators/**is outside the loader's push-dirs; the new files are loaded by Rails' generator system /require_relative.Test Coverage
spec/generators/migration_spec.rb— theMigrationvalue object: version coercion, block invocation with(root, generator),nil → []warnings, natural version ordering (1.10.0after1.1.0).spec/generators/migration_registry_spec.rb—applicablegap logic (exclusivefrom, ascending order, unknown/ahead sites, theuptoceiling filtering unreleased migrations) andmigrate!ordering + warning collection;.defaultships empty.spec/generators/install_generator_spec.rb— stamping (on install, injected into an existing initializer, updates a stale stamp, idempotent) and the--syncmigration wiring (runs for a stamped site and a pre-feature un-stamped site, prints warnings, no-op at current version, not on a full install).Verification
bundle exec rspec— 780 examples, 0 failures, 94.82% line coveragebundle exec rubocop— no offensesTest plan for a reviewer
https://claude.ai/code/session_01FPQb6z3YwcKRMbvoJhdxnX