plum-cms 0.2.2 → 0.2.3

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: 9daf7a444c4aca840d3395ef9e290708192a904b8d7effa9aa0954819ab3a046
4
- data.tar.gz: 5987aaecba9e612f37eae6cebb8ad663fcd39fc0add1342762836339bbaf5a3c
3
+ metadata.gz: a62b9b90b91530846a18cac552fcc7ee10cafcd67ef23ea135d12ff0d175f7e9
4
+ data.tar.gz: 7eada3af7d1000b9dbd526b3b7eab29af60de2790fad7cb7d5ddcb56146c8a68
5
5
  SHA512:
6
- metadata.gz: f7718730fa5d92db2a89d1ef13724d2a7cb0198322ff0bb239982390f4af3125747d3261ca1a61161da64d3dd45119a37d2396181dd7631d3193294428491781
7
- data.tar.gz: 9ddd57edac63fc212ac7ddfd20f3616551f84a437c72496b31d3f86d30fafdd4ac002454fd14a66e7bbe9faea6d274274e7fd69642790ded89bc29fe39c7596d
6
+ metadata.gz: f038e184d32a8fb9bb1c0449b7f67328d895f2974e0e71fd7e88d5b0f24be5329ad9c348d9d86e9115d0415507fb9366ac3988d121894691d23741d036e96ee7
7
+ data.tar.gz: eb22b20f634d5ff40d715c53c0c07d31109785305ed27dd6989fd44e923b89a6509efc4ff7289bb9aacf0788c008301c747cf088a888381510af9678e8bb8b04
data/CHANGELOG.md CHANGED
@@ -1,5 +1,76 @@
1
1
  # Changelog
2
2
 
3
+ ## Unreleased
4
+
5
+ - `plum deploy`'s health wait no longer requires a Dockerfile HEALTHCHECK:
6
+ images without one are probed via HTTP /up through the proxy instead
7
+ (resolved to loopback so SNI routing matches).
8
+ - Added `plum promote`: go-live for a preview, in place. The preview app
9
+ is re-homed onto the production domain via `once update --host` — same
10
+ container, same database, all content built during the preview — with
11
+ TLS provisioned on the spot. Preflights preview existence, hostname
12
+ collisions, and DNS. Verified live: a hostname re-home round-trip on a
13
+ running app served on the new domain within seconds, data intact.
14
+ - Added `plum deploy --preview` — the agency preview workflow: ship the
15
+ same image as a separate Once app under a preview hostname, shareable
16
+ with a client before their domain points anywhere. Uses `preview_host:`
17
+ from plum.yml (wildcard-record agency domains) or auto-derives
18
+ `<app>.<server-ip>.sslip.io` with zero DNS setup. Previews are
19
+ independent apps: they never touch the live site, and going live later
20
+ is a plain `plum deploy`.
21
+ - `plum deploy` now preflights DNS on first deploys: Once verifies
22
+ https://<hostname> as its final step and rolls the app back when the
23
+ domain doesn't reach the server, so the CLI checks A records against
24
+ the server's addresses before building anything and asks before
25
+ proceeding (proxied DNS like Cloudflare legitimately mismatches, hence
26
+ a confirm rather than a refusal).
27
+ - Fixed the install generator mounting Plum ABOVE the app's own routes:
28
+ Rails' `route` helper prepends, so an at-"/" mount shadowed everything
29
+ below it — including `get "up"`, the health check that load balancers
30
+ and deploy tooling (Once, kamal-proxy, plum deploy) probe, which made
31
+ freshly scaffolded sites fail health checks with Plum's 404 page. The
32
+ mount is now appended at the bottom of routes.rb with a comment
33
+ explaining why it must stay last. Found by the first real `plum new` →
34
+ `plum deploy` run.
35
+
36
+ - Fixed `plum connect` in a directory with no plum.yml silently targeting
37
+ the globally active project (running it from a fresh repo could show —
38
+ or with --reconfigure, rewrite — a different project's connection).
39
+ Connect now always means "set up the current directory"; only an
40
+ explicit `--project NAME` redirects it.
41
+ - Connect now finishes the journey: when the once app isn't deployed yet
42
+ it offers to build and ship right there (chains into plum deploy), and
43
+ connecting a project always makes it the active one — working in a
44
+ directory means that's the project you're "in". A brand-new site's app
45
+ question also now defaults to "a new app on this server (type its
46
+ hostname)" instead of pre-selecting an already-deployed app, which
47
+ would have silently pointed the repo at another site's container.
48
+ - Untangled project vs. remote in connect's wording: the wizard now asks
49
+ for an "Environment name" (production, staging — plum.yml's remotes)
50
+ with a line explaining the concept, and the closing hint no longer
51
+ prints the nonsensical `plum pull --project production` — it points at
52
+ `plum pull` from the directory, or `--project <site>` from anywhere.
53
+ Terminology now spelled out in docs/plum-cli.md: projects are sites,
54
+ remotes are environments.
55
+ - Connect's server pick-list now knows every stored server, not just the
56
+ plum-* SSH aliases: hosts from every registered project's remotes are
57
+ merged in (annotated "— used by <project>"), so a server appears even
58
+ when its alias offer was declined.
59
+ - Added `plum new NAME`: scaffold a deploy-ready Plum site in one command
60
+ — rails new, the plum-cms gem, the install generator mounted at `/`, a
61
+ prepared database, and a git history. Bootstraps its own toolchain:
62
+ missing Ruby or Rails are offered for install (mise → ruby → rails),
63
+ each step confirm-gated. Refuses clearly when run inside an existing
64
+ Rails app, and registers the new project in the global registry.
65
+ - Added `plum deploy [remote]`: registry-free deploys — build the image
66
+ locally for linux/amd64, stream it to the server over SSH
67
+ (docker save | docker load; no registry, no CI, no accounts), roll the
68
+ Once app to it, and wait for the container health check. Auto-update is
69
+ disabled for these apps: deploys are explicit, never by surprise.
70
+ - Added docs/zero-to-agency.md and a matching tutorial chapter: empty
71
+ directory → client sites on a $6 DigitalOcean droplet with `plum new`,
72
+ `plum connect`, `plum deploy`, and the pull/push/backup rhythm.
73
+
3
74
  ## 0.2.2 — 2026-08-13
4
75
 
5
76
  ### Fixed
data/README.md CHANGED
@@ -251,7 +251,9 @@ ergonomics for self-hosted Plum sites:
251
251
 
252
252
  ```sh
253
253
  plum tutorial # interactive guided tour, right in the terminal
254
+ plum new client-site # scaffold a deploy-ready site (installs Rails if needed)
254
255
  plum connect 203.0.113.5 # guided setup: SSH keys, server tooling, plum.yml
256
+ plum deploy # build locally, ship over SSH — no registry, no CI
255
257
  plum connect # already configured? verifies the setup instead
256
258
  plum pull # replace your local site with production's content
257
259
  plum push # apply plum/ config-as-code files to the remote
@@ -265,7 +267,9 @@ A Firebase-style project registry (`plum use`, `--project`) manages a fleet
265
267
  of sites from one machine. Every command degrades to plain, script-friendly
266
268
  output when piped or under `NO_COLOR`. See [the CLI guide](docs/plum-cli.md)
267
269
  for transports (`via: ssh|kamal|once`), fleet management, and how `pull`
268
- works across database engines.
270
+ works across database engines — and [the zero-to-agency
271
+ guide](docs/zero-to-agency.md) for running client sites on a single
272
+ cheap VPS with no registry, CI, or PaaS.
269
273
 
270
274
  ## Content API
271
275
 
@@ -8,7 +8,7 @@
8
8
  <%= favicon_link_tag "plum-mark.svg", type: "image/svg+xml" %>
9
9
  <%= stylesheet_link_tag "plum/control_panel", "data-turbo-track": "reload" %>
10
10
  <%= stylesheet_link_tag "lexxy", "data-turbo-track": "reload" %>
11
- <%= javascript_importmap_tags %>
11
+ <%= javascript_importmap_tags(importmap: Plum.importmap || Rails.application.importmap) %>
12
12
  <script type="module">import "plum/application"</script>
13
13
  <style>
14
14
  :root {
@@ -7,7 +7,7 @@
7
7
  <%= csp_meta_tag %>
8
8
  <%= favicon_link_tag "plum-mark.svg", type: "image/svg+xml" %>
9
9
  <%= stylesheet_link_tag "plum/control_panel", "data-turbo-track": "reload" %>
10
- <%= javascript_importmap_tags %>
10
+ <%= javascript_importmap_tags(importmap: Plum.importmap || Rails.application.importmap) %>
11
11
  <style>
12
12
  :root { --plum-accent: <%= Plum.configuration.cp_accent_color %>; }
13
13
  .bg-purple-600 { background-color: var(--plum-accent) !important; }
@@ -8,7 +8,7 @@
8
8
  <%= favicon_link_tag "plum-mark.svg", type: "image/svg+xml" %>
9
9
  <%= stylesheet_link_tag "plum/control_panel", "data-turbo-track": "reload" %>
10
10
  <%= stylesheet_link_tag "lexxy", "data-turbo-track": "reload" %>
11
- <%= javascript_importmap_tags %>
11
+ <%= javascript_importmap_tags(importmap: Plum.importmap || Rails.application.importmap) %>
12
12
  <script type="module">import "plum/application"</script>
13
13
  <style>
14
14
  :root { --plum-accent: <%= Plum.configuration.cp_accent_color %>; }
data/docs/plum-cli.md CHANGED
@@ -84,12 +84,78 @@ accepted since the content is public. The host key persists at
84
84
  `~/.config/plum/tutorial_host_ed25519` so returning visitors don't get
85
85
  host-key warnings.
86
86
 
87
+ ## plum new — zero to running site
88
+
89
+ ```
90
+ plum new client-site
91
+ ```
92
+
93
+ Scaffolds a deploy-ready Plum site: `rails new`, the plum-cms gem, the
94
+ install generator mounted at `/`, a prepared database, and a git history.
95
+ It bootstraps its own toolchain the way connect bootstraps a server — no
96
+ Ruby? It offers to install mise, then Ruby, then Rails, each step
97
+ confirm-gated. It also refuses (clearly) to run inside an existing Rails
98
+ app, and registers the new project in the global registry.
99
+
100
+ ## plum deploy — no registry, no CI
101
+
102
+ ```
103
+ plum deploy [remote]
104
+ ```
105
+
106
+ Ships the site with nothing but local Docker and SSH: builds the image
107
+ locally (`--platform linux/amd64`), streams it to the server through the
108
+ SSH connection (`docker save | gzip | ssh docker load` — no registry
109
+ account anywhere), then has Once roll the app to it (`once update
110
+ --image`, or `once deploy --host <once_app>` on first deploy) and waits
111
+ for the container's health check to go green. Auto-update is switched
112
+ off for these apps: there's no registry to poll, so the site changes
113
+ when you deploy it — never by surprise. Currently `via: once` remotes.
114
+
115
+ ```
116
+ plum deploy --preview
117
+ ```
118
+
119
+ Ships the same image as a **separate Once app under a preview hostname** —
120
+ the agency workflow: share the work-in-progress with a client before
121
+ their domain points anywhere. With `preview_host:` set on the remote in
122
+ plum.yml (e.g. `client.preview.your-agency.com` behind a wildcard A
123
+ record) you get clean branded preview URLs; with nothing configured it
124
+ auto-derives `<app>.<server-ip>.sslip.io` — magic wildcard DNS that
125
+ resolves to your server with zero setup. Preview and production are
126
+ independent apps, so previews never touch a live site.
127
+
128
+ ```
129
+ plum promote [remote]
130
+ ```
131
+
132
+ Go-live for a preview, in place: re-homes the preview app onto the
133
+ production domain (`once update --host`) — same container, same
134
+ database, all the content built during the preview — with TLS
135
+ provisioned on the spot. Preflights that a preview exists, that the
136
+ production hostname isn't already a separate app, and that DNS reaches
137
+ the server (confirm to override for proxied DNS).
138
+
139
+ The full story — one droplet, several client sites, honest costs — is
140
+ [the zero-to-agency guide](zero-to-agency.md).
141
+
87
142
  ## Two layers of configuration
88
143
 
89
144
  This mirrors the Firebase CLI's model deliberately, because it solves the
90
145
  same two problems: "how does this directory reach its server" and "how do I
91
146
  work with a project without cd-ing into its directory first."
92
147
 
148
+ Two words, two meanings — keep them straight:
149
+
150
+ - A **project** is a site: `the-final-word`, `client-site`. That's what
151
+ the registry, `plum use`, and `--project NAME` refer to.
152
+ - A **remote** is one *environment* of a site: `production`, `staging`.
153
+ That's the positional argument on commands — `plum pull production`,
154
+ `plum logs staging` — and the keys under `remotes:` in plum.yml.
155
+
156
+ So `plum pull --project client-site staging` means "the staging
157
+ environment of the client-site project."
158
+
93
159
  1. **`plum.yml`** — lives in a site's own repo, defines its **remotes**
94
160
  (servers). This is the source of truth for how to reach that one site.
95
161
  2. **The global project registry** (`~/.config/plum/config.yml`, or
@@ -0,0 +1,172 @@
1
+ # Zero to Agency
2
+
3
+ How to go from an empty directory to hosting client sites — with one
4
+ cheap VPS, the `plum` CLI, and nothing else. No GitHub required, no
5
+ container registry, no CI pipeline, no platform-as-a-service bill. Your
6
+ laptop builds, your server runs, SSH connects them.
7
+
8
+ The stack under the hood: Rails + Plum in a Docker container, managed by
9
+ [Once](https://once.com) (37signals' single-container deploy tool) behind
10
+ its bundled TLS-terminating proxy, on any Linux box. The examples use
11
+ DigitalOcean; any VPS provider works the same way.
12
+
13
+ ## What you need
14
+
15
+ - A [DigitalOcean](https://digitalocean.com) account (or any VPS provider)
16
+ - Docker on your laptop (Docker Desktop or Docker Engine) — `plum deploy`
17
+ builds images locally
18
+ - The `plum` CLI
19
+ - Ruby and Rails — and if you don't have them, `plum new` offers to
20
+ install both for you (via [mise](https://mise.jdx.dev))
21
+
22
+ ## 1. Create the server
23
+
24
+ In DigitalOcean: **Create → Droplet**. Ubuntu LTS, the $6/month size is
25
+ enough to start (pick $12 for headroom). Add your SSH key if you have one
26
+ — if you don't, that's fine: `plum connect` will generate one and offer
27
+ to copy it over with the root password DigitalOcean emails you. Note the
28
+ droplet's IP.
29
+
30
+ That's the last time you touch a cloud dashboard.
31
+
32
+ ## 2. Create the site
33
+
34
+ ```bash
35
+ plum new client-site
36
+ cd client-site
37
+ bin/rails server
38
+ ```
39
+
40
+ You have a running Plum site at `http://localhost:3000` — the control
41
+ panel is at `/cp`. Build the client's theme in `app/themes/`, model
42
+ their content types in the CP (or as YAML in `plum/` — see
43
+ `docs/config-as-code.md`), and load in their content.
44
+
45
+ ## 3. Connect the server
46
+
47
+ ```bash
48
+ plum connect 203.0.113.5 # your droplet's IP
49
+ ```
50
+
51
+ The wizard handles the entire first-contact ritual: SSH keys, passwordless
52
+ login, detecting that this is a Once-shaped app, and — on a fresh droplet —
53
+ **installing Docker Engine and Once for you** (each with your
54
+ confirmation). When it asks for the app hostname, give it the client's
55
+ real domain (`clientname.com`): that's how the server routes requests and
56
+ provisions TLS.
57
+
58
+ ## 4. Point DNS
59
+
60
+ At the client's DNS: an **A record** for their domain (and `www`) to the
61
+ droplet's IP. **This must happen before the first deploy** — Once
62
+ verifies `https://<domain>` end-to-end as the final step of a first
63
+ deploy and rolls back if the domain doesn't reach the server yet
64
+ (`plum deploy` checks this up front and warns you). Proxied DNS
65
+ (Cloudflare in Full strict mode) is fine.
66
+
67
+ Building ahead of a domain cutover? That's what previews are for:
68
+
69
+ ```bash
70
+ plum deploy --preview
71
+ ```
72
+
73
+ This ships the site as a *separate* Once app under a preview hostname —
74
+ by default `<app>.<server-ip>.sslip.io`, magic wildcard DNS that reaches
75
+ your server with zero configuration, so you have a shareable client
76
+ preview URL before any real DNS exists. For branded previews, point one
77
+ wildcard record (`*.preview.your-agency.com`) at the droplet and set
78
+ `preview_host: client.preview.your-agency.com` on the remote in
79
+ plum.yml.
80
+
81
+ Iterate on the preview as long as you like — build the content, tune the
82
+ theme, let the client click around. Then, when they flip DNS to the
83
+ droplet:
84
+
85
+ ```bash
86
+ plum promote
87
+ ```
88
+
89
+ The preview app *becomes* the site: same container, same database, every
90
+ word of content you built during the preview — it simply starts answering
91
+ to the real domain, with TLS provisioned on the spot. No rebuild, no
92
+ content migration, no separate go-live deploy. (If the site should live
93
+ on its own droplet instead, `plum connect` it there and `plum deploy` —
94
+ site archives carry the content across.)
95
+
96
+ ## 5. Deploy
97
+
98
+ ```bash
99
+ plum deploy
100
+ ```
101
+
102
+ Watch it go: the image builds on your laptop, streams to the server over
103
+ SSH (`docker save | docker load` — this is why no registry account
104
+ exists in this guide), Once starts it behind the proxy, and the CLI waits
105
+ for the container to report healthy. `https://clientname.com` is live.
106
+
107
+ Deploys are explicit — the site changes when you run `plum deploy`,
108
+ never by surprise. Ship again any time with the same command.
109
+
110
+ ## 6. The agency rhythm
111
+
112
+ Day-to-day, the CLI is your relationship with every client site:
113
+
114
+ ```bash
115
+ plum pull # production content → your laptop, any DB engine
116
+ plum push # content-model changes (plum/ YAML) → production
117
+ plum check # did anyone drift the model? (CI-friendly)
118
+ plum logs --follow # heroku-style tail, prettified
119
+ plum backup # timestamped archive on the server
120
+ plum run -- TASK # any rake task, any remote
121
+ ```
122
+
123
+ Content pulls down, structure pushes up. Clients edit in `/cp` on
124
+ production; you develop locally against a full copy of their real
125
+ content. Turn on **static caching** (`docs/static-caching.md`) and the
126
+ site serves rendered pages from disk — SQLite happily runs a
127
+ production site on the smallest droplet, because cached reads never
128
+ touch the database at all.
129
+
130
+ ## 7. The second client
131
+
132
+ This is where it becomes an agency:
133
+
134
+ ```bash
135
+ plum new second-client
136
+ cd second-client
137
+ plum connect # the host prompt offers your existing server
138
+ plum deploy
139
+ ```
140
+
141
+ Once runs each site in its own container on the same box, routing by
142
+ hostname, each with its own TLS certificate. A $6–12 droplet comfortably
143
+ hosts several small client sites; each new client is marginal-cost-zero
144
+ until the box runs hot. Manage the fleet from anywhere:
145
+
146
+ ```bash
147
+ plum projects list # every site you run
148
+ plum use second-client # switch the active project
149
+ plum pull --project client-site # or target one explicitly
150
+ ```
151
+
152
+ ## 8. When you outgrow one box
153
+
154
+ - **More sites than one droplet likes**: create a second droplet,
155
+ `plum connect` the new sites there. The registry doesn't care how many
156
+ servers are behind it.
157
+ - **A client needs Postgres** (heavy dynamic features): switch that app's
158
+ database and redeploy — `plum pull` still works across engines.
159
+ - **Backups off the box**: `plum backup` writes archives on the server;
160
+ Once's auto-backup handles scheduling. Pull copies down with `plum pull`
161
+ or scp them to storage you control.
162
+
163
+ ## Costs, honestly
164
+
165
+ | Thing | Cost |
166
+ | --- | --- |
167
+ | Droplet (several small sites) | $6–12/mo |
168
+ | Domains | the client's problem |
169
+ | Registry, CI, PaaS, per-seat CMS pricing | $0 — none exist in this setup |
170
+
171
+ The whole model: you charge for design and content strategy; the
172
+ infrastructure is a rounding error.
@@ -30,7 +30,19 @@ module Plum
30
30
  def mount_engine
31
31
  return if options[:skip_route]
32
32
 
33
- route %(mount Plum::Engine, at: "#{options[:mount_path]}")
33
+ # Mounted at "/", Plum serves every path the app doesn't claim — so
34
+ # it must come AFTER the app's own routes (notably `get "up"`, the
35
+ # health check load balancers and deploy tooling probe). Rails'
36
+ # `route` helper prepends, which would shadow them; append at the
37
+ # bottom of the block instead.
38
+ inject_into_file "config/routes.rb", before: /^end\s*\z/ do
39
+ <<~RUBY.indent(2)
40
+
41
+ # Plum last: mounted at #{options[:mount_path].inspect} it serves every path
42
+ # the app doesn't claim, so app routes (like /up) must come first.
43
+ mount Plum::Engine, at: #{options[:mount_path].inspect}
44
+ RUBY
45
+ end
34
46
  end
35
47
 
36
48
  def print_next_steps
data/lib/plum/engine.rb CHANGED
@@ -30,10 +30,17 @@ module Plum
30
30
  end
31
31
  end
32
32
 
33
- initializer "plum.importmap", before: "importmap" do |app|
34
- if app.config.respond_to?(:importmap)
35
- app.config.importmap.paths << root.join("config/plum_importmap.rb")
36
- end
33
+ # Build a Plum-scoped importmap instead of merging Plum's pins into the
34
+ # host app's global importmap. Plum's layouts render this map (host JS stack
35
+ # + Plum's additions), while the host app's own pages keep a clean importmap
36
+ # free of Plum's controllers/lexxy/activestorage pins. Runs after the host's
37
+ # importmap is configured so we can copy its paths.
38
+ initializer "plum.importmap", after: "importmap" do |app|
39
+ next unless defined?(Importmap::Map) && app.config.respond_to?(:importmap)
40
+
41
+ Plum.importmap = Importmap::Map.new
42
+ Array(app.config.importmap.paths).each { |path| Plum.importmap.draw(path) }
43
+ Plum.importmap.draw(root.join("config/plum_importmap.rb"))
37
44
  end
38
45
  end
39
46
  end
data/lib/plum/version.rb CHANGED
@@ -1,6 +1,6 @@
1
1
  module Plum
2
2
  module Version
3
- STRING = "0.2.2"
3
+ STRING = "0.2.3"
4
4
  end
5
5
 
6
6
  VERSION = Version::STRING
data/lib/plum.rb CHANGED
@@ -3,6 +3,13 @@ require_relative "plum/content_source"
3
3
  require_relative "plum/configuration"
4
4
 
5
5
  module Plum
6
+ class << self
7
+ # A Plum-scoped importmap (host pins + Plum's own pins), used only by Plum's
8
+ # layouts. Keeps Plum's controllers/lexxy/activestorage out of the host
9
+ # app's global importmap so a mounted Plum can't pollute the host's JS.
10
+ attr_accessor :importmap
11
+ end
12
+
6
13
  def self.table_name_prefix
7
14
  "plum_"
8
15
  end
metadata CHANGED
@@ -1,14 +1,14 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: plum-cms
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.2.2
4
+ version: 0.2.3
5
5
  platform: ruby
6
6
  authors:
7
7
  - Ben Simmons
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2026-08-13 00:00:00.000000000 Z
11
+ date: 2026-09-19 00:00:00.000000000 Z
12
12
  dependencies:
13
13
  - !ruby/object:Gem::Dependency
14
14
  name: bcrypt
@@ -180,16 +180,22 @@ dependencies:
180
180
  name: image_processing
181
181
  requirement: !ruby/object:Gem::Requirement
182
182
  requirements:
183
- - - "~>"
183
+ - - ">="
184
184
  - !ruby/object:Gem::Version
185
185
  version: '1.2'
186
+ - - "<"
187
+ - !ruby/object:Gem::Version
188
+ version: '3'
186
189
  type: :runtime
187
190
  prerelease: false
188
191
  version_requirements: !ruby/object:Gem::Requirement
189
192
  requirements:
190
- - - "~>"
193
+ - - ">="
191
194
  - !ruby/object:Gem::Version
192
195
  version: '1.2'
196
+ - - "<"
197
+ - !ruby/object:Gem::Version
198
+ version: '3'
193
199
  description: Plum is a Rails-native CMS engine with Hotwire control panel, Liquid
194
200
  themes, and site-scoped content.
195
201
  email:
@@ -404,6 +410,7 @@ files:
404
410
  - docs/statamic-parity.md
405
411
  - docs/static-caching.md
406
412
  - docs/vision.md
413
+ - docs/zero-to-agency.md
407
414
  - lib/generators/plum/install/install_generator.rb
408
415
  - lib/generators/plum/install/templates/plum_initializer.rb
409
416
  - lib/plum.rb