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 +4 -4
- data/CHANGELOG.md +130 -0
- data/README.md +96 -6
- data/lib/ask/skills/yamine/SKILL.md +40 -13
- data/lib/yamine/cli/boot.rb +311 -42
- data/lib/yamine/cli/system.rb +105 -26
- data/lib/yamine/cli/worktree.rb +145 -45
- data/lib/yamine/cli.rb +1 -1
- data/lib/yamine/database.rb +171 -4
- data/lib/yamine/probe.rb +136 -0
- data/lib/yamine/proxy.rb +6 -1
- data/lib/yamine/readiness.rb +27 -2
- data/lib/yamine/runner.rb +18 -10
- data/lib/yamine/version.rb +1 -1
- data/lib/yamine.rb +1 -0
- metadata +2 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 3987eef1f0bc39291b8d264fd4b569fcb54106c23e78150594f62d86ca2d0351
|
|
4
|
+
data.tar.gz: e98404e9655e78861c289cb320bf544564e07e65e06fe32a496d5a3ff4ee52b9
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
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
|
|
174
|
-
|
|
175
|
-
|
|
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
|
|
211
|
-
to start it
|
|
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
|
|
112
|
-
`
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
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
|
|
128
|
-
app, its directory, and `yamine start` — not the parent app's code.
|
|
129
|
-
|
|
130
|
-
the
|
|
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
|
|