docs-kit 1.0.5 → 1.0.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: cf583f1cfdfc22d788c0dcffac91b35a374138d8670cfe24bdef3cc2e05eb171
4
- data.tar.gz: 7c3168bcaf1d0d1939350d704a12cb39f0e37a561be75a255517dcf82e531eba
3
+ metadata.gz: fa41c41824768d1db2a5ad5a7ba9b983b0f81032c8b5a3f80277e2611e203449
4
+ data.tar.gz: 60cd31b54d83c9c6920ff48d27ec177f81b7a240fdf513e65f9617837cc6c738
5
5
  SHA512:
6
- metadata.gz: 43c6f2f94b23ca4ad589a701f92121b3dcc3027cc06173432a653e215e9c4da2ab62e1a2044cf9ef3b1e19be9516d7a2698fc6b259f390ce3a15e5c2df50f6eb
7
- data.tar.gz: 001253000ff36f16cd9ab76377cb4175dcab526a5a8dc2897fd0bde9809016d0dbd42fe5aa1e6474c24e7260eed25468843c393a4e73d447dc01f16c1a87b489
6
+ metadata.gz: e23f649705ac6216e4961c389eab64965961023123937364e4915b608264bf994ec0885c1958929884449d4eb4f6d9202ed6a5fa7fb9dd373c6a2202e8850ea1
7
+ data.tar.gz: 0a047f0b8b45fdce21b9121b13461a5c003ec307586ef4b49bbb3ead0c4d79f44db00a243eda7792fc5be173f3b135195efad4212360527cb2ef5e143eafd5e5
data/CHANGELOG.md CHANGED
@@ -18,6 +18,13 @@
18
18
 
19
19
  ### Added
20
20
 
21
+ - **`DocsUI::Landing` hero logo (`c.landing.logo`).** The landing hero now takes
22
+ an optional brand mark above the eyebrow, in two forms: an inline single-path
23
+ SVG (`{ svg: "<path d>", viewbox:, label: }`, rendered with `fill: currentColor`
24
+ so it adapts to the theme) or an image (`{ src:, alt: }`, resolved through the
25
+ site's asset pipeline). nil (the default) omits it, so it's backwards-compatible.
26
+ This closes the gap a mounted docs app hit — a hero with no way to show the
27
+ product's own logo — the first consumer to try `DocsUI::Landing` on a real site.
21
28
  - **`DocsUI::Landing` — a config-driven marketing landing page.** Every consuming
22
29
  site (and this dogfood site) was hand-rolling a home page; now render
23
30
  `DocsUI::Landing` and drive it from a new `c.landing` config block
data/README.md CHANGED
@@ -81,6 +81,31 @@ site-owned content (your `Doc` registry, your pages, your themed
81
81
  `application.tailwind.css`). Drop `--sync` to also (re)scaffold missing content
82
82
  files — Thor prompts before overwriting anything that exists.
83
83
 
84
+ ### Version-aware migrations
85
+
86
+ Every install and `--sync` stamps the docs-kit version it ran into your config
87
+ initializer, as an inert comment on the first line:
88
+
89
+ ```ruby
90
+ # docs-kit synced: v1.0.5
91
+ # frozen_string_literal: true
92
+ Rails.application.config.to_prepare do
93
+ DocsKit.configure do |c|
94
+ # ...
95
+ ```
96
+
97
+ On the next `--sync` after a `bundle update docs-kit`, the generator reads that
98
+ stamp, works out the gap to the newly-installed version, and runs the **ordered
99
+ release-to-release migrations** in between — renamed config knobs, changed route
100
+ shapes, restructured templates — in sequence, then restamps to the new version.
101
+ A site created before the stamp existed (no comment) is treated as the earliest
102
+ version, so it gets every migration. Migrations are **warn-only-safe** exactly
103
+ like the drift report: what a step can't safely automate it prints as a manual
104
+ checklist (`migration steps to apply by hand:`), never a destructive rewrite of
105
+ a line you've edited. There are no migrations to apply yet — the mechanism ships
106
+ ahead of the first release that needs one, so the upgrade path is already in
107
+ place.
108
+
84
109
  ### One-time cleanup for sites created before these landed
85
110
 
86
111
  `--sync` detects drift it can't safely automate and prints a checklist — it
@@ -132,11 +157,15 @@ diff Dockerfile "$(bundle show docs-kit)/lib/generators/docs_kit/install/templat
132
157
  # config/initializers/docs_kit.rb
133
158
  DocsKit.configure do |c|
134
159
  c.brand = "phlex-reactive"
135
- c.brand_href = "/docs" # brand link target (default "/")
160
+ c.brand_href = "/docs" # the DOCS home: brand, sidebar, and "← Docs home" masthead link (default "/")
136
161
  c.title_suffix = "phlex-reactive"
137
162
  c.themes = %w[dark light synthwave retro cyberpunk dracula night nord sunset]
138
163
  c.version_badge = -> { "v#{Phlex::Reactive::VERSION}" } # optional
139
164
 
165
+ # Docs embedded in a bigger app? The way BACK to it — a labeled link rendered
166
+ # once in the topbar, right after the brand. Unset (default) renders nothing.
167
+ c.app_link = { href: "/", label: "Back to the app" }
168
+
140
169
  # Repo/social links in the topbar (next to the theme switcher).
141
170
  c.topbar_links = [
142
171
  { href: "https://github.com/you/phlex-reactive", label: "GitHub", icon: :github },
@@ -156,13 +185,15 @@ registry maps a heading to its authored pages (`Doc.nav_items`); a page that
156
185
  isn't written yet is skipped, so there are no dead links. Register a page with
157
186
  one line (see [Add a page](#add-a-page)) and it appears in the sidebar.
158
187
 
159
- ### Brand link and dark code themes
188
+ ### The two homes, the brand link, and dark code themes
160
189
 
161
- Three knobs cover what sites used to shim by subclassing `DocsUI::Shell`:
190
+ These knobs cover what sites used to shim by subclassing `DocsUI::Shell` or
191
+ overriding route helpers:
162
192
 
163
193
  | Knob | Default | What it does |
164
194
  |------|---------|--------------|
165
- | `c.brand_href` | `"/"` | The href of the topbar brand link. Set it (e.g. `"/docs"`) instead of subclassing `Shell` to copy-paste `#topbar`. |
195
+ | `c.brand_href` | `"/"` | The **docs home** — the href of the topbar brand, the sidebar brand, and each page's "← Docs home" masthead link. Set it (e.g. `"/docs"`) when the docs live under a subpath, instead of subclassing `Shell` or overriding `root_path`. |
196
+ | `c.app_link` | `nil` | The **app home** — an opt-in `{ href:, label: }` link back to the application hosting the docs, rendered once in the topbar right after the brand (e.g. `{ href: "/", label: "Back to the app" }`). Unset renders nothing, so a standalone docs site is unchanged. External hrefs open in a new tab with `rel=noopener`. |
166
197
  | `c.code_theme_dark` | `nil` | A second Rouge theme for **dark** daisyUI themes. `nil` keeps the single-theme behavior (fully backwards compatible). When set, `DocsUI::Code` also emits this theme's CSS scoped under `[data-theme=X] .code-highlight` for each shipped dark theme, so code blocks stay readable when the switcher flips to a dark theme. |
167
198
  | `c.dark_themes` | daisyUI's built-in dark theme names | Which theme names count as dark for `code_theme_dark`. Intersected with `c.themes` at render time, so only shipped themes emit CSS. Override to name custom dark themes (e.g. `%w[zazu-dark]`). |
168
199
 
@@ -1,13 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DocsUI
4
- # The marketing landing page — a hero (eyebrow + title + lead + optional install
5
- # snippet + CTA buttons), a feature-card grid, and a registry-grouped
6
- # documentation index — rendered inside DocsUI::Shell. Every consuming site was
7
- # hand-rolling this; drive it from config instead:
4
+ # The marketing landing page — a hero (an optional brand logo + eyebrow + title +
5
+ # lead + optional install snippet + CTA buttons), a feature-card grid, and a
6
+ # registry-grouped documentation index — rendered inside DocsUI::Shell. Every
7
+ # consuming site was hand-rolling this; drive it from config instead:
8
8
  #
9
9
  # # config/initializers/docs_kit.rb
10
10
  # DocsKit.configure do |c|
11
+ # c.landing.logo = { svg: "M4 2h9l5 5…Z", viewbox: "0 0 22 24", label: "Acme" }
11
12
  # c.landing.eyebrow = "Developer Docs"
12
13
  # c.landing.title = "Jobs & events on **Postgres**" # ** ** → primary color
13
14
  # c.landing.lead = "PostgreSQL-native jobs + event bus for Rails."
@@ -30,6 +31,9 @@ module DocsUI
30
31
  # walks the same #docs-content region Shell stamps.
31
32
  class Landing < Phlex::HTML
32
33
  include Phlex::Rails::Helpers::Request
34
+ # For the image-form hero logo (c.landing.logo = { src: … }): resolve the asset
35
+ # path through the site's pipeline to its digested /assets URL.
36
+ include Phlex::Rails::Helpers::ImageURL
33
37
 
34
38
  def view_template
35
39
  render DocsUI::Shell.new(title: landing.eyebrow || config.brand) do
@@ -50,6 +54,7 @@ module DocsUI
50
54
 
51
55
  def hero
52
56
  div(class: "flex flex-col gap-6") do
57
+ logo
53
58
  eyebrow
54
59
  heading
55
60
  lead
@@ -58,6 +63,22 @@ module DocsUI
58
63
  end
59
64
  end
60
65
 
66
+ # The brand mark — an inline single-path SVG (currentColor, theme-adaptive) or
67
+ # an <img>. Rendered above the eyebrow, like a product wordmark.
68
+ def logo
69
+ return unless (mark = landing.hero_logo)
70
+
71
+ if mark.inline?
72
+ svg(viewbox: mark.viewbox, class: "h-9 w-auto text-primary", fill: "currentColor",
73
+ role: "img", aria_label: mark.label) do |s|
74
+ s.title { mark.label } if mark.label
75
+ s.path(d: mark.svg)
76
+ end
77
+ else
78
+ img(src: image_url(mark.src), alt: mark.alt.to_s, class: "h-9 w-auto")
79
+ end
80
+ end
81
+
61
82
  def eyebrow
62
83
  return unless (text = landing.eyebrow)
63
84
 
@@ -65,7 +65,7 @@ module DocsUI
65
65
  # "Markdown" action sits opposite "← Home"; it's chrome too, so it lives
66
66
  # inside the skipped nav and never appears in the .md twin.
67
67
  nav(class: "mb-6 flex items-center justify-between gap-4", data: { md_skip: true }) do
68
- a(href: root_path, class: "link link-hover text-sm opacity-70") { "← Home" }
68
+ a(href: home_href, class: "link link-hover text-sm opacity-70") { "← Docs home" }
69
69
  render DocsUI::MarkdownAction.new(request.path) if markdown_action?
70
70
  end
71
71
 
@@ -92,5 +92,12 @@ module DocsUI
92
92
  def content
93
93
  raise NotImplementedError, "#{self.class} must implement #content"
94
94
  end
95
+
96
+ private
97
+
98
+ # The masthead "← Docs home" target: config.brand_href (the DOCS home, like
99
+ # the topbar/sidebar brand links) — never the host's root_path helper, which
100
+ # on an app-embedded site is the application root, not the docs landing.
101
+ def home_href = DocsKit.configuration.brand_href
95
102
  end
96
103
  end
@@ -157,6 +157,7 @@ module DocsUI
157
157
  label(for: DRAWER_ID, class: "btn btn-square btn-ghost btn-sm lg:hidden",
158
158
  aria_label: "Open menu") { render DocsUI::Icon.new("menu", class: "size-5") }
159
159
  a(href: config.brand_href, class: "btn btn-ghost text-lg font-bold") { config.brand }
160
+ app_home_link
160
161
  end
161
162
  render DocsUI::SearchBox.new if config.search_enabled?
162
163
  div(class: "flex-none items-center") do
@@ -167,5 +168,22 @@ module DocsUI
167
168
  end
168
169
  end
169
170
  end
171
+
172
+ # The opt-in App Home link (config.app_link) — the way back to the hosting
173
+ # app, rendered once, right after the brand. Nothing renders when unset, so
174
+ # the topbar stays byte-identical for a site that never configures it.
175
+ # External hrefs open in a new tab with rel=noopener (same posture as
176
+ # DocsUI::TopbarLinks); a site-relative href opens in place.
177
+ def app_home_link
178
+ link = config.app_link
179
+ return unless link
180
+
181
+ a(
182
+ href: link.href,
183
+ class: "link link-hover text-sm opacity-70",
184
+ target: (link.external? ? "_blank" : nil),
185
+ rel: (link.external? ? "noopener noreferrer" : nil)
186
+ ) { link.label }
187
+ end
170
188
  end
171
189
  end
@@ -184,6 +184,15 @@ module DocsKit
184
184
  # Read the effective map via #api_clients (which merges), never @api_clients.
185
185
  attr_writer :api_clients
186
186
 
187
+ # The opt-in "App Home" link — the way back to the application that hosts
188
+ # the docs, rendered ONCE in the topbar right after the brand (e.g.
189
+ # "Back to the app" on a docs site embedded in a bigger app). A Hash
190
+ # ({ href:, label: }) or a DocsKit::TopbarLink; #app_link normalizes it.
191
+ # Defaults to nil → no link renders and the topbar is byte-identical to
192
+ # before. Distinct from #brand_href, which is the DOCS home (the brand,
193
+ # sidebar, and page-masthead links). Read via #app_link, never @app_link.
194
+ attr_writer :app_link
195
+
187
196
  # External links rendered in the topbar next to the theme switcher — a repo
188
197
  # link, a chat invite, a social profile. Each entry is a Hash
189
198
  # ({ href:, label:, icon: }) or a DocsKit::TopbarLink; #topbar_links
@@ -262,10 +271,19 @@ module DocsKit
262
271
  @api_base_url = "https://api.example.com"
263
272
  @api_auth_header = nil
264
273
  @api_clients = {}
274
+ @app_link = nil
265
275
  @topbar_links = []
266
276
  @openapi = nil
267
277
  end
268
278
 
279
+ # The normalized App Home link (a DocsKit::TopbarLink), or nil when unset —
280
+ # absent config, absent link, exactly like every other opt-in knob.
281
+ def app_link
282
+ return if @app_link.nil?
283
+
284
+ DocsKit::TopbarLink.from(@app_link)
285
+ end
286
+
269
287
  # The normalized topbar links (DocsKit::TopbarLink list), in declaration
270
288
  # order. Each configured Hash/TopbarLink is coerced via TopbarLink.from, so
271
289
  # the Shell only ever sees value objects. Blank/nil config yields [].
@@ -26,6 +26,15 @@ module DocsKit
26
26
  # individually assignable in the `c.landing.x = ...` block, mirroring
27
27
  # DocsKit::SeoConfig.
28
28
  class LandingConfig
29
+ # An optional brand logo/mark rendered at the top of the hero (above the
30
+ # eyebrow), like a product wordmark. A Hash in one of two forms:
31
+ # { svg: "<path d …>", viewbox: "0 0 81 45", label: "Brand" } # inline mark
32
+ # { src: "logo.svg", alt: "Brand" } # image asset/URL
33
+ # The inline `svg` form renders with `fill: currentColor` so it adapts to the
34
+ # theme (light/dark) — best for a single-color mark. nil omits the logo.
35
+ # See #hero_logo (the normalized Logo value object) and DocsUI::Landing.
36
+ attr_writer :logo
37
+
29
38
  # A small uppercase kicker above the title (e.g. "Developer Docs"). nil omits it.
30
39
  attr_accessor :eyebrow
31
40
 
@@ -81,6 +90,13 @@ module DocsKit
81
90
  { code: attrs[:code].to_s, filename: attrs[:filename], lexer: (attrs[:lexer] || :shell).to_sym }
82
91
  end
83
92
 
93
+ # The hero logo as a normalized Logo value object, or nil when unset.
94
+ def hero_logo
95
+ return if @logo.nil?
96
+
97
+ Logo.from(@logo)
98
+ end
99
+
84
100
  # One hero call-to-action button. `style` maps to a daisyUI btn variant
85
101
  # (:primary → btn-primary, anything else → btn-ghost). `icon` is an optional
86
102
  # brand/lucide token rendered before the label (DocsUI::BrandMark resolves it).
@@ -117,5 +133,28 @@ module DocsKit
117
133
  new(icon: attrs[:icon], title: attrs[:title], body: attrs[:body])
118
134
  end
119
135
  end
136
+
137
+ # The hero brand logo — either an inline single-path SVG mark (`svg` = the
138
+ # `<path d>` data, `viewbox` = its viewBox) rendered with fill: currentColor so
139
+ # it adapts to the theme, OR an image (`src` = an asset path/URL, `alt` = its
140
+ # accessible name). `label` is the accessible name for the inline mark.
141
+ Logo = Data.define(:svg, :viewbox, :src, :alt, :label) do
142
+ def initialize(svg: nil, viewbox: "0 0 24 24", src: nil, alt: nil, label: nil)
143
+ super
144
+ end
145
+
146
+ def self.from(logo)
147
+ return logo if logo.is_a?(self)
148
+
149
+ attrs = logo.to_h.transform_keys(&:to_sym)
150
+ new(
151
+ svg: attrs[:svg], viewbox: attrs[:viewbox] || "0 0 24 24",
152
+ src: attrs[:src], alt: attrs[:alt], label: attrs[:label]
153
+ )
154
+ end
155
+
156
+ # An inline SVG mark (vs. an <img>). True when `svg` path data is present.
157
+ def inline? = !svg.to_s.empty?
158
+ end
120
159
  end
121
160
  end
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module DocsKit
4
- VERSION = "1.0.5"
4
+ VERSION = "1.0.7"
5
5
  end
@@ -4,6 +4,7 @@ require "erb"
4
4
  require "yaml"
5
5
  require "rails/generators/base"
6
6
  require_relative "sync_report"
7
+ require_relative "migration_registry"
7
8
  require_relative "../../../docs_kit/version"
8
9
 
9
10
  module DocsKit
@@ -44,6 +45,18 @@ module DocsKit
44
45
  AGENTS_END = "<!-- END docs-kit -->"
45
46
  AGENTS_BLOCK_RE = /#{Regexp.escape(AGENTS_BEGIN)}.*#{Regexp.escape(AGENTS_END)}/m
46
47
 
48
+ # The config initializer — the one file every site has, so it carries the
49
+ # last-synced version stamp. See stamp_synced_version / run_migrations.
50
+ INITIALIZER = "config/initializers/docs_kit.rb"
51
+
52
+ # The inert comment recording which docs-kit version last synced this site.
53
+ # A future `--sync` reads it to run only the ordered migrations between that
54
+ # version and the gem's current one. Absent → the site predates the stamp
55
+ # (treated as the earliest version, so every migration applies).
56
+ SYNCED_STAMP_RE = /^#\s*docs-kit synced:\s*v(\d+\.\d+\.\d+)\s*$/
57
+
58
+ def self.synced_stamp(version = DocsKit::VERSION) = "# docs-kit synced: v#{version}"
59
+
47
60
  # The RuboCop wiring docs-kit injects. REQUIRE loads the cops;
48
61
  # INHERIT_GEM/INHERIT_PATH enable + scope them (see config/rubocop/docs_kit.yml).
49
62
  RUBOCOP_REQUIRE = "docs_kit/rubocop"
@@ -308,6 +321,43 @@ module DocsKit
308
321
  report.items.each { |item| say " • #{item}" }
309
322
  end
310
323
 
324
+ # Run the ordered release-to-release migrations between the site's
325
+ # last-synced version and the gem's current one — the payoff of the version
326
+ # stamp. `--sync` only (a full install is a fresh scaffold, not an upgrade),
327
+ # and BEFORE stamp_synced_version restamps, so it reads the OLD version. An
328
+ # un-stamped site is treated as the earliest version (every migration runs).
329
+ # Migrations are warn-only-safe: what they can't automate they hand back as
330
+ # a checklist, printed like the drift report. The registry ships EMPTY at
331
+ # 1.0.x, so today this is a no-op that establishes the upgrade path.
332
+ def run_migrations
333
+ return unless options[:sync]
334
+
335
+ warnings = MigrationRegistry.default.migrate!(synced_version, destination_root, self)
336
+ return if warnings.empty?
337
+
338
+ say_status :warn, "migration steps to apply by hand:", :yellow
339
+ warnings.each { |item| say " • #{item}" }
340
+ end
341
+
342
+ # Record which docs-kit version this site is now synced at, so the NEXT
343
+ # `--sync` can run only the migrations after it. Injected as an inert
344
+ # comment at the top of the initializer (the one file every site has) —
345
+ # which create_initializer never rewrites, so stamping is its own step.
346
+ # Idempotent: updates a stale stamp in place, adds one when absent, and
347
+ # is a no-op when already current.
348
+ def stamp_synced_version
349
+ path = File.join(destination_root, INITIALIZER)
350
+ return unless File.exist?(path)
351
+
352
+ current = self.class.synced_stamp
353
+ source = File.read(path)
354
+ updated = source.match?(SYNCED_STAMP_RE) ? source.sub(SYNCED_STAMP_RE, current) : "#{current}\n#{source}"
355
+ return if updated == source
356
+
357
+ File.write(path, updated)
358
+ say_status :update, "#{INITIALIZER} (synced v#{DocsKit::VERSION})", :green
359
+ end
360
+
311
361
  def show_post_install
312
362
  return show_sync_summary if options[:sync]
313
363
 
@@ -328,6 +378,17 @@ module DocsKit
328
378
 
329
379
  private
330
380
 
381
+ # The docs-kit version this site was last synced at, read from the
382
+ # initializer's stamp. Un-stamped (a site created before the stamp landed,
383
+ # or a fresh skeleton) → "0.0.0", the earliest version, so every migration
384
+ # applies. Read BEFORE stamp_synced_version overwrites it.
385
+ def synced_version
386
+ path = File.join(destination_root, INITIALIZER)
387
+ return "0.0.0" unless File.exist?(path)
388
+
389
+ File.read(path)[SYNCED_STAMP_RE, 1] || "0.0.0"
390
+ end
391
+
331
392
  def show_sync_summary
332
393
  say_status :info, "docs-kit synced.", :green
333
394
  say <<~MSG
@@ -0,0 +1,32 @@
1
+ # frozen_string_literal: true
2
+
3
+ module DocsKit
4
+ module Generators
5
+ # One ordered, versioned upgrade step between two docs-kit releases. `to` is
6
+ # the version the migration belongs to (the release that introduced the
7
+ # change); the registry runs it when a site's last-synced version is BELOW
8
+ # `to`. The block receives the site's `(destination_root, generator)` so it
9
+ # can read/rewrite files via the generator's helpers, and returns the list
10
+ # of manual-cleanup warnings it could NOT safely automate.
11
+ #
12
+ # Warn-only-safe by contract (the #24 drift pattern): a migration does what
13
+ # it can idempotently — never a destructive rewrite of a hand-edited line —
14
+ # and hands back strings for whatever needs a human. A `nil` return means
15
+ # "nothing to warn about".
16
+ class Migration
17
+ attr_reader :to, :description
18
+
19
+ def initialize(to:, description:, &block)
20
+ @to = Gem::Version.new(to.to_s)
21
+ @description = description
22
+ @block = block
23
+ end
24
+
25
+ # Run the transform against the site. Returns the (possibly empty) list of
26
+ # manual-cleanup warnings — never nil, so callers can flat-map safely.
27
+ def call(root, generator)
28
+ Array(@block.call(root, generator))
29
+ end
30
+ end
31
+ end
32
+ end
@@ -0,0 +1,57 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "migration"
4
+ require_relative "../../../docs_kit/version"
5
+
6
+ module DocsKit
7
+ module Generators
8
+ # The ordered set of release-to-release migrations `--sync` applies. Given a
9
+ # site's last-synced version, it selects the migrations the site hasn't run
10
+ # yet — those in the half-open range `(from_version, upto]`: ABOVE the site
11
+ # version (a migration AT the site version is already applied) and no newer
12
+ # than the installed gem — and runs them in ascending order, collecting the
13
+ # warn-only messages each couldn't safely automate.
14
+ #
15
+ # The `upto` ceiling (default: the installed gem version) matters because
16
+ # `--sync` restamps the site to DocsKit::VERSION afterward. A migration whose
17
+ # `to` sat ABOVE the gem version would then still exceed the new stamp and
18
+ # re-run on EVERY sync forever. It can't legitimately exist anyway (a site
19
+ # can't have "arrived" at a release it doesn't have), so it's filtered out.
20
+ #
21
+ # `.default` is the registry the generator uses. It SHIPS EMPTY at 1.0.x —
22
+ # the mechanism (stamp the synced version, detect the gap, run ordered
23
+ # transforms) is the deliverable; the first concrete `1.x → 1.y` transform is
24
+ # a one-line `Migration.new(...)` addition here once a release needs one.
25
+ class MigrationRegistry
26
+ def initialize(migrations = [])
27
+ @migrations = migrations.sort_by(&:to)
28
+ end
29
+
30
+ # The registry the install generator runs during `--sync`. Empty today.
31
+ def self.default
32
+ @default ||= new(MIGRATIONS)
33
+ end
34
+
35
+ # No migrations to register yet — the mechanism is the 1.0 deliverable.
36
+ # Add ordered `Migration.new(to: "1.x.0", description: "...") { ... }`
37
+ # entries here as future releases change config knobs, routes, or templates.
38
+ MIGRATIONS = [].freeze
39
+
40
+ # The migrations a site last synced at `from_version` still needs, ascending
41
+ # by version — those in `(from_version, upto]`. `upto` defaults to the
42
+ # installed gem version so a migration targeting an unreleased version never
43
+ # applies (and never re-runs against the post-sync stamp).
44
+ def applicable(from_version, upto: DocsKit::VERSION)
45
+ from = Gem::Version.new(from_version.to_s)
46
+ ceiling = Gem::Version.new(upto.to_s)
47
+ @migrations.select { |migration| migration.to > from && migration.to <= ceiling }
48
+ end
49
+
50
+ # Run every applicable migration in order against the site, returning the
51
+ # flattened list of manual-cleanup warnings they couldn't safely automate.
52
+ def migrate!(from_version, root, generator, upto: DocsKit::VERSION)
53
+ applicable(from_version, upto: upto).flat_map { |migration| migration.call(root, generator) }
54
+ end
55
+ end
56
+ end
57
+ end
@@ -1,3 +1,4 @@
1
+ <%= self.class.synced_stamp %>
1
2
  # frozen_string_literal: true
2
3
 
3
4
  # docs-kit configuration — everything that makes this site look like YOUR docs.
@@ -20,6 +21,12 @@ Rails.application.config.to_prepare do
20
21
  # docs live under a subpath:
21
22
  # c.brand_href = "/docs"
22
23
 
24
+ # Docs embedded in a bigger app? Add the way BACK to that app — a labeled
25
+ # link rendered once in the topbar, right after the brand. brand_href is the
26
+ # DOCS home (brand, sidebar, and the page masthead's "← Docs home" all
27
+ # follow it); app_link is the APP home. Unset (the default) renders nothing.
28
+ # c.app_link = { href: "/", label: "Back to the app" }
29
+
23
30
  # Repo/social links in the topbar, next to the theme switcher. Each renders
24
31
  # as an icon-only button; `icon` is a shipped brand mark (:github, :gitlab,
25
32
  # :discord, :x, :rubygems, :bluesky, :mastodon, :slack, :whatsapp, :telegram,
@@ -109,6 +116,9 @@ Rails.application.config.to_prepare do
109
116
  # knobs. Every field is optional; with none set it still renders a minimal hero
110
117
  # (the brand + the doc index). Wrap a run in **double asterisks** to accent it
111
118
  # in the primary color.
119
+ # c.landing.logo = { svg: "M4 2h9l5 5…Z", viewbox: "0 0 22 24", label: "<%= app_brand %>" }
120
+ # # ^ an inline single-path SVG mark (fill: currentColor, theme-adaptive), OR
121
+ # # an image: { src: "logo.svg", alt: "<%= app_brand %>" }
112
122
  # c.landing.eyebrow = "Developer Docs"
113
123
  # c.landing.title = "The <%= app_brand %> **API**"
114
124
  # c.landing.lead = "One sentence on what your product does."
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: docs-kit
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.0.5
4
+ version: 1.0.7
5
5
  platform: ruby
6
6
  authors:
7
7
  - Mikael Henriksson
@@ -234,6 +234,8 @@ files:
234
234
  - lib/docs_kit/version.rb
235
235
  - lib/generators/docs_kit/install/USAGE
236
236
  - lib/generators/docs_kit/install/install_generator.rb
237
+ - lib/generators/docs_kit/install/migration.rb
238
+ - lib/generators/docs_kit/install/migration_registry.rb
237
239
  - lib/generators/docs_kit/install/sync_report.rb
238
240
  - lib/generators/docs_kit/install/templates/Dockerfile.tt
239
241
  - lib/generators/docs_kit/install/templates/agents_md.erb