yamine 0.15.2 → 0.17.0

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: f0fb00156c920d8b49a4116be04c671904877e26cb75816d3f7d05d899f413be
4
- data.tar.gz: 28c31ed6f7e03fd453b9fa00c1372c36701b675b015c9de906223934b3ade84f
3
+ metadata.gz: 3987eef1f0bc39291b8d264fd4b569fcb54106c23e78150594f62d86ca2d0351
4
+ data.tar.gz: e98404e9655e78861c289cb320bf544564e07e65e06fe32a496d5a3ff4ee52b9
5
5
  SHA512:
6
- metadata.gz: 69ce5134433ef830e42ba29420ab0ab17a6f39ec39cb885e534cbdae485cc6360270ad45b6e31b99191988a0f2d3fd6d20b5a0073df9469478ebeddc0f092c03
7
- data.tar.gz: 037bc4d5fd9ac1767304c1a6bee32572bc28a95cd64fde42414c6ff38624dd74c3a0be05966bd2d32a91e5c3664ba99ac1a5fac50fcd81b1a673a48e75b990b6
6
+ metadata.gz: 20018aea0d2229ab0c6c814d15deaeb176649fcdaa81937eb2c6f223607e50f2c34256ed078aca6ef0bb0a114cbd865bd6187dd2f74a1b981d2dbd7c8d7e141d
7
+ data.tar.gz: 808f2b4151de99fa3fe71ba8bbbfce6eb7dbe63298bea1da3c4fd05a5baea9da7f5d797ae792057f8ddd2f5500b19da47fd97af1641481764eb8f051605a0c0f
data/CHANGELOG.md CHANGED
@@ -1,5 +1,135 @@
1
1
  # Changelog
2
2
 
3
+ ## [Unreleased]
4
+
5
+ ## [0.17.0] — 2026-09-23
6
+
7
+ ### Added
8
+
9
+ - **Per-worktree `.env` files — hand-run isolation without touching the
10
+ app's config.** `worktree add` writes `.env` and `.env.development`
11
+ (the whole development set: `DATABASE_URL` + one `NAME_DATABASE_URL`
12
+ per configuration) and `.env.test` (`PRIMARY_DATABASE_URL` — the key
13
+ Rails checks *before* `DATABASE_URL` for a flat test config, so the
14
+ test URL wins no matter what order a loader reads the files in).
15
+ Any dotenv loader picks them up — hand-run `rails console`,
16
+ `rails test`, and `db:migrate` land on the worktree's databases with
17
+ zero yamine-specific code in `database.yml`. Mode 0600, git-excluded
18
+ automatically, removed with the worktree.
19
+
20
+ ### Changed
21
+
22
+ - **`.yamine-db-suffix` (0.16.0) is replaced by the `.env` files above.**
23
+ The database.yml suffix hook is no longer needed — apps keep a plain
24
+ boring config. Legacy marker files are deleted on sight.
25
+ - **The conformance check now diagnoses precisely.** `worktree add`
26
+ re-probes after writing `.env` and distinguishes the two ways an app
27
+ can fail to isolate hand-run commands: no dotenv loader loaded the
28
+ file (fix: one Gemfile line), vs `database.yml` supplies `url:` keys,
29
+ which Rails gives *precedence over the entire environment* —
30
+ `merge_db_environment_variables` skips URL-shaped configs, so neither
31
+ injected env nor `.env` can ever redirect them (fix: component form —
32
+ `database:`, `host:`, `username:` — for development/test).
33
+ - **Worktrees copy only the credential keys their own environments
34
+ need**: `config/master.key`, `config/credentials/development.key`,
35
+ `config/credentials/test.key`. Production and staging keys no longer
36
+ travel into throwaway directories.
37
+
38
+ ### Fixed
39
+
40
+ - **`worktree add` could purge the MAIN checkout's test database.**
41
+ The per-worktree test schema preparation ran under
42
+ `RAILS_ENV=development`, where the flat test config resolves to the
43
+ BASE name (the environment-override merge only applies to the current
44
+ environment's configs) — so `db:test:prepare` purged and reloaded
45
+ *main's* `myrr_markdown_test` during a worktree add, observed live.
46
+ It now runs under `RAILS_ENV=test`, where dotenv loads `.env.test`
47
+ and the override lands on the worktree's own test database; a
48
+ regression test pins the environment.
49
+
50
+ ## [0.16.0] — 2026-09-23
51
+
52
+ ### Added
53
+
54
+ - **Multi-database worktrees — the whole set, asked not guessed.**
55
+ `worktree add` now probes the app itself (`bin/rails runner` resolves
56
+ database.yml + credentials inside the app process; yamine never parses
57
+ config or touches a key) and provisions every database it declares:
58
+ each name gains a collision-guarded per-worktree suffix, the claim
59
+ records all names plus the server coordinates, schemas load once, and
60
+ `remove`/`clean` drop the entire set as a unit — orphaned worktrees
61
+ drop from the claim alone, without the app even booting. Boot injects
62
+ `DATABASE_URL` plus `NAME_DATABASE_URL` per configuration (Rails' own
63
+ convention), so supervised processes are isolated even in apps that
64
+ never heard of yamine. New surface: `yamine db describe` prints what
65
+ the app actually resolves (passwords masked), `db list` and
66
+ `worktree list` show every database under a claim, and `db create`
67
+ re-probes — run it after the app grows a database to self-heal every
68
+ worktree.
69
+ - **`.yamine-db-suffix` — hand-run commands isolate too.** `add` writes
70
+ a token file (git-excluded automatically); a four-line suffix hook at
71
+ the top of `config/local.yml`'s sibling `database.yml` (see README)
72
+ makes a hand-run `rails console`, `rails test`, or `db:migrate` in
73
+ the worktree resolve to the worktree's databases — env injection only
74
+ ever reaches processes yamine spawns. Conformance is proven at
75
+ creation ("database.yml reads .yamine-db-suffix"); an app without the
76
+ hook still gets fully isolated boots via env, and gets told exactly
77
+ what the gap is at that moment.
78
+ - **The test database joins the claim.** It is probed and suffixed like
79
+ the rest, schema-prepared on first creation (`db:test:prepare` — Rails
80
+ will not load schema into an empty test database by itself), and
81
+ dropped at teardown — a fresh worktree runs `rails test` as-is, and
82
+ no suffixed test database is ever left behind.
83
+ - **Credential keys travel to worktrees.** `add` now copies
84
+ `config/master.key` and `config/credentials/*.key` (mode 0600) with
85
+ the rest of the per-checkout config — a credentials app could not
86
+ even boot in a fresh worktree before, let alone resolve its
87
+ databases.
88
+
89
+ ### Changed
90
+
91
+ - **The main checkout of a Rails app boots in silence.** The
92
+ "app looks database-backed but no DATABASE_URL template found"
93
+ warning no longer fires on a main checkout — the app's own databases
94
+ ARE its databases; isolation is a worktree concern, and worktrees
95
+ get it from the probe.
96
+ - **`clean` no longer gets stuck forever on unresolvable legacy
97
+ claims.** A pre-multi-database claim whose app declares no template
98
+ (credentials apps never did) could never be resolved by any future
99
+ command, so every `worktree clean` anywhere failed on it until it was
100
+ removed by hand. Such claims are now forgotten with a warning that
101
+ names the database, so a real leftover stays droppable by hand while
102
+ `clean` moves on.
103
+ - The claims file (`databases.json`) is written mode 0600 —
104
+ multi-database claims carry the app's resolved connection URLs.
105
+
106
+ ### Fixed
107
+
108
+ - **Every real Postgres drop has always crashed.** `pg_drop` passed
109
+ the environment as a keyword to `Open3.capture2` (spawn takes it
110
+ positionally), so the moment a `dropdb` was actually attempted —
111
+ `worktree remove`, `worktree clean`, any teardown against a reachable
112
+ server — it raised `ArgumentError` mid-teardown, after routes were
113
+ already stopped. Unit tests never saw it (an unreachable server
114
+ returns before the spawn). Drops now run, verified against a real
115
+ five-database app end to end.
116
+ - **Boots of dependency-less Node stubs failed forever.** A committed
117
+ `package.json` with no dependencies (`{}`) demanded a `node_modules`
118
+ directory that `npm install` itself will never create — every fresh
119
+ clone and worktree of such an app failed its pre-flight. The check
120
+ now fires only when the manifest actually declares dependencies (an
121
+ unparseable manifest keeps the old conservative behavior).
122
+ - **A hostname with no route answers 503, not 404.** The proxy was
123
+ answering 404 for "no app registered for this hostname" — the same status
124
+ an app gives for a path it does not have. A machine client (a health
125
+ check, an API client, an agent) that trusts the status concluded the app
126
+ had answered and went looking for a bug in the app's routes, when the app
127
+ was not running at all. It now answers 503 Service Unavailable with the
128
+ same helpful page (parent app, its directory, `yamine start`), so the
129
+ status alone says the app is not there — matching the 502 a registered
130
+ route with a dead backend already gets. Foreign hosts keep their bare 404
131
+ that names nothing.
132
+
3
133
  ## [0.15.1] — 2026-09-22
4
134
 
5
135
  ### Fixed
data/README.md CHANGED
@@ -170,9 +170,11 @@ yamine worktree clean # tear down everything already merged
170
170
  ```
171
171
 
172
172
  `add` lands the worktree beside the repo, copies the gitignored
173
- per-checkout config (`config/local.yml`, `config/local.secrets`) the
174
- branch needs, runs `bundle install`, and pre-creates the per-worktree
175
- database with schema — the next step is just `yamine start` in it.
173
+ per-checkout config (`config/local.yml`, `config/local.secrets`,
174
+ `config/master.key`, and the development/test credential keys) the branch needs, runs
175
+ `bundle install`, asks the app what databases it has, and provisions
176
+ the whole set with schema — the next step is just `yamine start` in
177
+ it.
176
178
 
177
179
  `clean` is the done-and-merged sweep: it tears down every worktree
178
180
  whose branch is merged (stop, drop database, remove worktree, delete
@@ -190,6 +192,93 @@ yamine --variant demo # -> https://demo.myapp.localhos
190
192
  yamine --tld preview.example.com # your own domain (OAuth parity)
191
193
  ```
192
194
 
195
+ ### Multi-database apps
196
+
197
+ A Rails multi-database app (five databases is normal: primary, cache,
198
+ queue, cable, errors) gets **the whole set** per worktree — asked, not
199
+ guessed. `worktree add` boots `bin/rails runner` once inside the app so
200
+ database.yml and credentials resolve exactly as the app would resolve
201
+ them (yamine never parses config or touches a key), suffixes every
202
+ database name with a collision-guarded per-worktree token, creates and
203
+ schema-loads them, prepares the test database, and records names plus
204
+ server coordinates in the claim. Boot injects `DATABASE_URL` and one
205
+ `NAME_DATABASE_URL` per configuration (Rails' own convention).
206
+ `remove`/`clean` drop the entire set as a unit — including from an
207
+ orphaned claim whose directory is already gone, without the app
208
+ booting.
209
+
210
+ ```bash
211
+ yamine db describe # what THIS checkout resolves to (passwords masked)
212
+ yamine db list # every claim, every database under it
213
+ yamine db create # re-probe + provision (run after the app grows a database)
214
+ ```
215
+
216
+ **Two one-time app requirements, both boring:**
217
+
218
+ 1. *Component-form development/test config.* Environment overrides —
219
+ injected `DATABASE_URL`/`NAME_DATABASE_URL` and the per-worktree
220
+ `.env` — only apply to component keys (`database:`, `host:`). A
221
+ `url:` key (the usual credentials-driven style) takes precedence
222
+ over the *entire* environment: Rails skips URL-shaped configs when
223
+ merging environment variables, so nothing injected or loaded can
224
+ redirect them. Development and test should read like a plain Rails
225
+ file:
226
+
227
+ ```yaml
228
+ default: &default
229
+ adapter: postgresql
230
+ encoding: unicode
231
+ host: <%= ENV.fetch("DB_HOST", "localhost") %>
232
+ username: postgres
233
+
234
+ development:
235
+ primary:
236
+ <<: *default
237
+ database: myapp_development
238
+ cache:
239
+ <<: *default
240
+ database: myapp_development_cache
241
+ migrations_paths: db/cache_migrate
242
+
243
+ staging:
244
+ primary: &primary_staging
245
+ <<: *default
246
+ url: <%= Rails.application.credentials.dig(:database, :primary, :url) %>
247
+ ```
248
+
249
+ Staging/production keep doing whatever they do — yamine only ever
250
+ redirects development and test.
251
+
252
+ 2. *A dotenv loader* — `gem "dotenv-rails", groups: [:development, :test]`
253
+ (any dotenv loader works). That is what reads the files
254
+ `worktree add` writes into the worktree:
255
+
256
+ - `.env` / `.env.development` — the whole development set
257
+ (`DATABASE_URL` plus one `NAME_DATABASE_URL` per configuration),
258
+ - `.env.test` — the test URL under `PRIMARY_DATABASE_URL`, the key
259
+ Rails checks *before* `DATABASE_URL` for a flat test config, so it
260
+ wins regardless of the order a loader reads the files in.
261
+
262
+ Mode 0600, git-excluded automatically, removed with the worktree.
263
+ A hand-run `rails console`, `rails test`, or `db:migrate` in the
264
+ worktree therefore lands on the worktree's own databases — with
265
+ **zero yamine-specific code in `database.yml`, ever**.
266
+
267
+ `worktree add` proves both after writing the files: you will see
268
+ `.env loaded — hand-run commands are isolated too`. A warning instead
269
+ names which requirement is missing — no loader ran, or the config is
270
+ `url:`-shaped — and the exact fix. Supervised boots are isolated via
271
+ injected env either way (component form permitting).
272
+
273
+ Credential keys travel too: `config/master.key`,
274
+ `config/credentials/development.key`, and
275
+ `config/credentials/test.key` are copied at mode 0600. Production and
276
+ staging keys stay in the main checkout where they belong.
277
+
278
+ The main checkout never gets `.env` files or a suffix: its databases
279
+ are its databases, untouched.
280
+
281
+
193
282
  ## Subdomains are opt-in
194
283
 
195
284
  A route answers its exact hostname. `*.myapp.localhost` reaches
@@ -207,8 +296,9 @@ yamine alias tenant1 4001 --wildcard # one route, its subdomains
207
296
  Off is the useful default. An unregistered label under a live app is far
208
297
  more likely to be a worktree whose stack is stopped than a tenant, and
209
298
  handing that label to the parent app means HTTP 200 with the wrong code.
210
- Instead the request 404s and names the parent app, its directory, and how
211
- to start it. `yamine status` reports which mode an app is in.
299
+ Instead the request fails with 503 and names the parent app, its directory,
300
+ and how to start it — the app is not there, which is not the same statement
301
+ as the app answering 404. `yamine status` reports which mode an app is in.
212
302
 
213
303
  ## Commands
214
304
 
@@ -225,7 +315,7 @@ yamine open [name] # open the app URL in a browser
225
315
  yamine trust # add local CA to system trust store
226
316
  yamine clean # remove state and hosts entries
227
317
  yamine prune # remove stale routes
228
- yamine db list|create|drop # per-worktree databases
318
+ yamine db list|create|drop|describe # per-worktree databases (multi-database aware)
229
319
  yamine worktree list|add|remove|clean # worktree lifecycle
230
320
  yamine stop # stop this app's backend + routes
231
321
  yamine restart # touch tmp/restart.txt (managed apps reboot)
@@ -108,15 +108,40 @@ yamine worktree clean [--dry-run]
108
108
  ```
109
109
 
110
110
  `add` creates the git worktree beside the repo, copies the gitignored
111
- per-checkout config (`config/local.yml`, `config/local.secrets`), runs
112
- `bundle install`, and pre-creates the per-worktree database — then boot
113
- with `yamine start` inside it. `clean` tears down every worktree whose
114
- branch is merged (stops backends, drops the database, removes the
115
- worktree, deletes the branch) and forgets claims of directories that
116
- vanished. It never touches uncommitted work; unmerged branches survive
117
- everything except `remove --force`. Prefer `clean --dry-run` first, and
118
- `clean` over `rm -rf` — a removed worktree leaves no routes, database,
119
- or stale hosts entries behind.
111
+ per-checkout config (`config/local.yml`, `config/local.secrets`,
112
+ `config/master.key`, and the development/test credential keys — never
113
+ production/staging keys), runs `bundle install`, **asks the app what
114
+ databases it has** (one `bin/rails runner` probe — database.yml and
115
+ credentials resolve inside the app; yamine never parses them), and
116
+ provisions the whole set with schema — every database of a
117
+ multi-database app gets a per-worktree suffix, the test database
118
+ included and schema-prepared, so `rails test` runs as-is. It writes
119
+ `.env` / `.env.development` (the development set) and `.env.test`
120
+ (`PRIMARY_DATABASE_URL` — the key Rails checks first for a flat test
121
+ config) into the worktree, git-excluded automatically. Then boot with
122
+ `yamine start` inside it. `remove` and `clean` drop the entire set as
123
+ a unit — including from an orphaned claim whose directory is gone,
124
+ without booting the app — and `clean` never touches uncommitted work;
125
+ unmerged branches survive everything except `remove --force`. Prefer
126
+ `clean --dry-run` first, and `clean` over `rm -rf` — a removed
127
+ worktree leaves no routes, databases, or stale hosts entries behind.
128
+
129
+ `yamine db describe` shows what the current checkout resolves to
130
+ (passwords masked); `yamine db list` shows every database under every
131
+ claim; `yamine db create` re-probes and self-heals (run it in each
132
+ worktree after the app grows a database).
133
+
134
+ Isolation reaches two places: supervised boots via injected
135
+ `DATABASE_URL` / `NAME_DATABASE_URL` env vars, and hand-run commands
136
+ (`rails console`, `rails test`, `db:migrate`) via the `.env` files —
137
+ which require a dotenv loader in the app (`gem "dotenv-rails",
138
+ groups: [:development, :test]`) and **component-form development/test
139
+ config** (`database:` keys, never `url:` — a `url:` key takes
140
+ precedence over the entire environment, so nothing injected or loaded
141
+ can redirect it; staging/production URLs are fine, yamine never
142
+ touches those envs). `worktree add` verifies both and prints
143
+ `.env loaded — hand-run commands are isolated too`; its warning names
144
+ exactly which requirement is missing and the fix.
120
145
 
121
146
  ## Subdomains are opt-in
122
147
 
@@ -124,10 +149,12 @@ A route answers its exact hostname. `*.myapp.localhost` reaches the app
124
149
  only if it asked (`proxy.subdomains: true` in `config/local.yml`, or
125
150
  `yamine alias <name> <port> --wildcard` for one route).
126
151
 
127
- So a worktree whose stack is not running gets a 404 that names the parent
128
- app, its directory, and `yamine start` — not the parent app's code. If
129
- you hit a `.localhost` URL that loads but looks wrong, check you started
130
- the worktree you think you did; `yamine status` in it prints the URL.
152
+ So a worktree whose stack is not running gets a 503 that names the parent
153
+ app, its directory, and `yamine start` — not the parent app's code. The
154
+ status says the app is not there, so a script can tell "not running" from
155
+ "the app answered 404". If you hit a `.localhost` URL that loads but looks
156
+ wrong, check you started the worktree you think you did; `yamine status` in
157
+ it prints the URL.
131
158
 
132
159
  ## First time on a machine
133
160