yamine 0.17.0 → 0.18.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 +22 -0
- data/README.md +11 -4
- data/lib/ask/skills/yamine/SKILL.md +5 -3
- data/lib/yamine/database.rb +61 -30
- data/lib/yamine/version.rb +1 -1
- metadata +1 -1
checksums.yaml
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
---
|
|
2
2
|
SHA256:
|
|
3
|
-
metadata.gz:
|
|
4
|
-
data.tar.gz:
|
|
3
|
+
metadata.gz: 43b8c059b0635ce4272b806fd2b8f70ae42ed6d591ed87536de9c86ed0419864
|
|
4
|
+
data.tar.gz: c34fb3a40d5c16442accee4dcbdb32b1b2b5013de5c3a823bade745b6ac65e88
|
|
5
5
|
SHA512:
|
|
6
|
-
metadata.gz:
|
|
7
|
-
data.tar.gz:
|
|
6
|
+
metadata.gz: 70495a26f226c3b0dac853c5a4ce1694207f4a9bbb73af92b44b37cc17ff3c13430af7440252102b8acd40f22ee7efdae5dfec26155678a5839e30b10c5c0d1a
|
|
7
|
+
data.tar.gz: e450abc81d00dad86bf03e540304ad43683e99329a60eb24ddea0122760048c88729e7fe33c9b93056a1840ec871e488ea458d1afdbd655b0dccd477369b8cb5
|
data/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,28 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.18.0] — 2026-09-23
|
|
6
|
+
|
|
7
|
+
### Changed
|
|
8
|
+
|
|
9
|
+
- **Only environment-scoped env files — never plain `.env`.**
|
|
10
|
+
`worktree add` writes `.env.development` + `.env.test`; the plain
|
|
11
|
+
`.env` it also wrote in 0.17.0 is gone. Plain `.env` is the file
|
|
12
|
+
production-style tooling reads by name — kamal, docker
|
|
13
|
+
`--env-file`, and dotenv itself loads `.env` in *every* environment
|
|
14
|
+
— so a worktree's copy could leak development database URLs into a
|
|
15
|
+
production boot that happened to run from that directory.
|
|
16
|
+
`.env.development` / `.env.test` are invisible to a production boot
|
|
17
|
+
by construction. A yamine-authored `.env` from 0.17.0 is cleaned up
|
|
18
|
+
on sight (header-marked); a foreign `.env` — app-committed or
|
|
19
|
+
hand-written — is never touched.
|
|
20
|
+
- **Env-file writes upsert instead of overwrite.** yamine's keys are
|
|
21
|
+
(re)written; every other line in the file survives, so a committed
|
|
22
|
+
env file's config or a developer's copied-in API keys are no longer
|
|
23
|
+
destroyed by `yamine db create`. The yamine header stays
|
|
24
|
+
idempotent across re-runs.
|
|
25
|
+
|
|
26
|
+
|
|
5
27
|
## [0.17.0] — 2026-09-23
|
|
6
28
|
|
|
7
29
|
### Added
|
data/README.md
CHANGED
|
@@ -253,13 +253,20 @@ yamine db create # re-probe + provision (run after the app grows a da
|
|
|
253
253
|
(any dotenv loader works). That is what reads the files
|
|
254
254
|
`worktree add` writes into the worktree:
|
|
255
255
|
|
|
256
|
-
- `.env
|
|
257
|
-
|
|
256
|
+
- `.env.development` — the whole development set (`DATABASE_URL`
|
|
257
|
+
plus one `NAME_DATABASE_URL` per configuration),
|
|
258
258
|
- `.env.test` — the test URL under `PRIMARY_DATABASE_URL`, the key
|
|
259
259
|
Rails checks *before* `DATABASE_URL` for a flat test config, so it
|
|
260
260
|
wins regardless of the order a loader reads the files in.
|
|
261
261
|
|
|
262
|
-
|
|
262
|
+
Only these environment-scoped names are ever written — **never
|
|
263
|
+
plain `.env`**, the file production tooling reads by name (kamal,
|
|
264
|
+
docker `--env-file`, and dotenv itself loads `.env` in *every*
|
|
265
|
+
environment), so a production boot can never see a worktree's
|
|
266
|
+
database URLs. Mode 0600, git-excluded automatically, removed with
|
|
267
|
+
the worktree. Existing keys in those files are **upserted, never
|
|
268
|
+
clobbered** — your own entries (API keys, a committed env file's
|
|
269
|
+
config) survive `yamine db create`.
|
|
263
270
|
A hand-run `rails console`, `rails test`, or `db:migrate` in the
|
|
264
271
|
worktree therefore lands on the worktree's own databases — with
|
|
265
272
|
**zero yamine-specific code in `database.yml`, ever**.
|
|
@@ -275,7 +282,7 @@ Credential keys travel too: `config/master.key`,
|
|
|
275
282
|
`config/credentials/test.key` are copied at mode 0600. Production and
|
|
276
283
|
staging keys stay in the main checkout where they belong.
|
|
277
284
|
|
|
278
|
-
The main checkout never gets
|
|
285
|
+
The main checkout never gets these env files or a suffix: its databases
|
|
279
286
|
are its databases, untouched.
|
|
280
287
|
|
|
281
288
|
|
|
@@ -116,9 +116,11 @@ credentials resolve inside the app; yamine never parses them), and
|
|
|
116
116
|
provisions the whole set with schema — every database of a
|
|
117
117
|
multi-database app gets a per-worktree suffix, the test database
|
|
118
118
|
included and schema-prepared, so `rails test` runs as-is. It writes
|
|
119
|
-
`.env
|
|
119
|
+
`.env.development` (the development set) and `.env.test`
|
|
120
120
|
(`PRIMARY_DATABASE_URL` — the key Rails checks first for a flat test
|
|
121
|
-
config) into the worktree, git-excluded automatically
|
|
121
|
+
config) into the worktree, git-excluded automatically — never plain `.env`
|
|
122
|
+
(production tooling reads that name), and existing keys in those files
|
|
123
|
+
are upserted, not clobbered. Then boot with
|
|
122
124
|
`yamine start` inside it. `remove` and `clean` drop the entire set as
|
|
123
125
|
a unit — including from an orphaned claim whose directory is gone,
|
|
124
126
|
without booting the app — and `clean` never touches uncommitted work;
|
|
@@ -133,7 +135,7 @@ worktree after the app grows a database).
|
|
|
133
135
|
|
|
134
136
|
Isolation reaches two places: supervised boots via injected
|
|
135
137
|
`DATABASE_URL` / `NAME_DATABASE_URL` env vars, and hand-run commands
|
|
136
|
-
(`rails console`, `rails test`, `db:migrate`) via
|
|
138
|
+
(`rails console`, `rails test`, `db:migrate`) via these env files —
|
|
137
139
|
which require a dotenv loader in the app (`gem "dotenv-rails",
|
|
138
140
|
groups: [:development, :test]`) and **component-form development/test
|
|
139
141
|
config** (`database:` keys, never `url:` — a `url:` key takes
|
data/lib/yamine/database.rb
CHANGED
|
@@ -32,17 +32,29 @@ module Yamine
|
|
|
32
32
|
|
|
33
33
|
MAX_IDENTIFIER_BYTES = 63
|
|
34
34
|
STATE_FILE = "databases.json"
|
|
35
|
-
# The per-worktree environment files yamine writes
|
|
36
|
-
#
|
|
35
|
+
# The per-worktree environment files yamine writes — ONLY
|
|
36
|
+
# environment-scoped names, never plain `.env`. Production-style
|
|
37
|
+
# tooling reads `.env` by name (kamal, docker --env-file, and
|
|
38
|
+
# dotenv itself loads `.env` in *every* environment), so a
|
|
39
|
+
# worktree's plain `.env` could leak dev database URLs into a
|
|
40
|
+
# production boot that happened to run from this directory.
|
|
41
|
+
# `.env.development` / `.env.test` are invisible to a production
|
|
42
|
+
# boot by construction — dotenv only ever reads
|
|
43
|
+
# `.env.#{Rails.env}` for the current environment.
|
|
44
|
+
#
|
|
37
45
|
# `.env.test` carries PRIMARY_DATABASE_URL — the key Rails checks
|
|
38
|
-
# *before* DATABASE_URL for a flat test config, so the test URL
|
|
39
|
-
# no matter which order a loader reads the
|
|
40
|
-
# commands (`rails console`, `rails test`, `db:migrate`) pick
|
|
41
|
-
# up; env injection still covers processes yamine spawns
|
|
42
|
-
ENV_FILES = %w[.env
|
|
43
|
-
# 0.16.0's marker file — removed in favor of
|
|
44
|
-
# so upgraded worktrees don't accumulate dead files.
|
|
46
|
+
# *before* DATABASE_URL for a flat test config, so the test URL
|
|
47
|
+
# wins no matter which order a loader reads the files in. Hand-run
|
|
48
|
+
# commands (`rails console`, `rails test`, `db:migrate`) pick
|
|
49
|
+
# these up; env injection still covers processes yamine spawns.
|
|
50
|
+
ENV_FILES = %w[.env.development .env.test].freeze
|
|
51
|
+
# 0.16.0's marker file — removed in favor of env files; deleted on
|
|
52
|
+
# sight so upgraded worktrees don't accumulate dead files.
|
|
45
53
|
LEGACY_MARKER_FILE = ".yamine-db-suffix"
|
|
54
|
+
# 0.17.0 wrote plain `.env` too; this header marks it as ours to
|
|
55
|
+
# delete. A `.env` WITHOUT the header belongs to the app or the
|
|
56
|
+
# developer and is never touched.
|
|
57
|
+
LEGACY_ENV_HEADER = "written by yamine"
|
|
46
58
|
|
|
47
59
|
# Database name for a worktree dir + env (development/test).
|
|
48
60
|
# Main checkout (no worktree marker) keeps the bare name.
|
|
@@ -176,44 +188,63 @@ module Yamine
|
|
|
176
188
|
name
|
|
177
189
|
end
|
|
178
190
|
|
|
179
|
-
# Write the worktree's environment files
|
|
180
|
-
#
|
|
181
|
-
#
|
|
182
|
-
#
|
|
183
|
-
#
|
|
184
|
-
# 0.16.0 marker
|
|
191
|
+
# Write the worktree's environment files (see ENV_FILES for the
|
|
192
|
+
# why of the names). Upsert semantics: our keys are (re)written,
|
|
193
|
+
# every other line in the file survives — a committed env file's
|
|
194
|
+
# non-database config, or a developer's copied-in API keys, must
|
|
195
|
+
# not be destroyed by `yamine db create`. Private mode; the
|
|
196
|
+
# 0.16.0 marker and a yamine-authored 0.17.0 `.env` are removed
|
|
197
|
+
# on the way past.
|
|
185
198
|
def write_env_files(dir, dev_env, test_url: nil)
|
|
186
199
|
FileUtils.rm_f(File.join(dir, LEGACY_MARKER_FILE))
|
|
200
|
+
remove_legacy_worktree_env(dir)
|
|
187
201
|
return if dev_env.nil? && test_url.nil?
|
|
188
202
|
|
|
189
203
|
unless dev_env.nil? || dev_env.empty?
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
# Same content under the env-specific name: a loader that
|
|
193
|
-
# prefers .env.development over .env finds identical values.
|
|
194
|
-
write_env_file(File.join(dir, ".env.development"), body)
|
|
204
|
+
merge_env_file(File.join(dir, ".env.development"),
|
|
205
|
+
dev_env.reject { |k, _v| k == "PRIMARY_DATABASE_URL" })
|
|
195
206
|
end
|
|
196
207
|
return if test_url.nil? || test_url.to_s.empty?
|
|
197
208
|
|
|
198
|
-
|
|
199
|
-
|
|
209
|
+
merge_env_file(File.join(dir, ".env.test"),
|
|
210
|
+
{ "PRIMARY_DATABASE_URL" => test_url })
|
|
200
211
|
end
|
|
201
212
|
|
|
202
|
-
|
|
213
|
+
# 0.17.0 wrote plain `.env` — remove it ONLY when yamine authored
|
|
214
|
+
# it; an app-committed or developer-written `.env` is theirs.
|
|
215
|
+
def remove_legacy_worktree_env(dir)
|
|
216
|
+
path = File.join(dir, ".env")
|
|
217
|
+
return unless File.file?(path)
|
|
218
|
+
return unless File.read(path).include?(LEGACY_ENV_HEADER)
|
|
219
|
+
|
|
220
|
+
FileUtils.rm_f(path)
|
|
221
|
+
end
|
|
222
|
+
|
|
223
|
+
# Upsert our keys into a dotenv file, preserving every other line.
|
|
224
|
+
# Our header is placed once (idempotent across re-runs).
|
|
225
|
+
def merge_env_file(path, env)
|
|
226
|
+
existing = File.file?(path) ? File.read(path).lines : []
|
|
227
|
+
key_res = env.keys.map { |k| /\A\s*#{Regexp.escape(k)}\s*=/ }
|
|
228
|
+
kept = existing.reject { |line| key_res.any? { |re| line.match?(re) } }
|
|
229
|
+
unless kept.any? { |line| line.include?(LEGACY_ENV_HEADER) }
|
|
230
|
+
kept = yamine_env_header.lines + kept
|
|
231
|
+
end
|
|
232
|
+
body = kept.join
|
|
233
|
+
body += "\n" unless body.empty? || body.end_with?("\n")
|
|
234
|
+
body += env.map { |k, v| "#{k}='#{dotenv_escape(v.to_s)}'\n" }.join
|
|
203
235
|
File.write(path, body)
|
|
204
236
|
File.chmod(0o600, path)
|
|
205
237
|
end
|
|
206
238
|
|
|
239
|
+
def yamine_env_header
|
|
240
|
+
"# Per-worktree databases, written by yamine (worktree add).\n" \
|
|
241
|
+
"# Environment-scoped (.env.development/.env.test) so a production\n" \
|
|
242
|
+
"# boot never reads these; other keys in this file are preserved.\n"
|
|
243
|
+
end
|
|
244
|
+
|
|
207
245
|
# dotenv format: single-quoted values (literal — no interpolation
|
|
208
246
|
# of a password's `$` or `#`), with the two escapes the dotenv
|
|
209
247
|
# grammar allows inside them.
|
|
210
|
-
def dotenv_body(env)
|
|
211
|
-
header = "# Per-worktree databases, written by yamine (worktree add).\n" \
|
|
212
|
-
"# Loaded by dotenv-rails / any dotenv loader; removed with this worktree.\n"
|
|
213
|
-
lines = env.map { |k, v| "#{k}='#{dotenv_escape(v.to_s)}'" }
|
|
214
|
-
"#{header}#{lines.join("\n")}\n"
|
|
215
|
-
end
|
|
216
|
-
|
|
217
248
|
def dotenv_escape(value)
|
|
218
249
|
value.gsub("\\") { "\\\\" }.gsub("'") { "\\'" }
|
|
219
250
|
end
|
data/lib/yamine/version.rb
CHANGED