ferrflow --dry-run migrate runs the source config and writes ferrflow.json. The flag is accepted and silently ignored.
Reproduced
$ ls
release.config.mjs
$ ferrflow migrate --dry-run
Migrated semantic-release config from release.config.mjs → ferrflow.json
$ ls
ferrflow.json release.config.mjs
Exit 0, no warning that the flag did nothing.
Cause
The dispatch arm drops it (src/cli.rs):
Commands::Migrate { from } => crate::config::migrate(from.map(Into::into)),
self.dry_run is never read, migrate takes no such parameter, and write_and_report calls std::fs::write(filename, &content) unconditionally. So the flag parses, passes validation, and has no effect.
Why it matters more than a missing feature
--dry-run is a global flag, and every other mutating command honours it. Someone reaching for it on an unfamiliar repo is doing exactly the careful thing, and gets the opposite of what they asked for: the config executes (it is a JavaScript program, see #1093) and a file lands in their working tree.
The blast radius is small, ferrflow.json is refused if one already exists, so this cannot overwrite a real config. It is the silence that is wrong, not the damage.
Options
- Honour it. Thread
dry_run into migrate, print the config that would be written, skip the write. This is what the flag means everywhere else and what a reader expects.
- Reject it. Fail with "migrate does not support --dry-run" rather than accepting a flag that does nothing.
Option 1 is the useful one: previewing a migration before it touches the repo is worth having, and the report already prints what was mapped and what needs review. Note it cannot make the command side-effect free, because reading a JavaScript config means executing it, so the preview still runs the user's code. That limit is worth stating wherever the flag is documented.
Found while documenting the execution behaviour in #1094, where I first got this wrong in the opposite direction and claimed the dry run skipped the write.
ferrflow --dry-run migrateruns the source config and writesferrflow.json. The flag is accepted and silently ignored.Reproduced
Exit 0, no warning that the flag did nothing.
Cause
The dispatch arm drops it (
src/cli.rs):self.dry_runis never read,migratetakes no such parameter, andwrite_and_reportcallsstd::fs::write(filename, &content)unconditionally. So the flag parses, passes validation, and has no effect.Why it matters more than a missing feature
--dry-runis a global flag, and every other mutating command honours it. Someone reaching for it on an unfamiliar repo is doing exactly the careful thing, and gets the opposite of what they asked for: the config executes (it is a JavaScript program, see #1093) and a file lands in their working tree.The blast radius is small,
ferrflow.jsonis refused if one already exists, so this cannot overwrite a real config. It is the silence that is wrong, not the damage.Options
dry_runintomigrate, print the config that would be written, skip the write. This is what the flag means everywhere else and what a reader expects.Option 1 is the useful one: previewing a migration before it touches the repo is worth having, and the report already prints what was mapped and what needs review. Note it cannot make the command side-effect free, because reading a JavaScript config means executing it, so the preview still runs the user's code. That limit is worth stating wherever the flag is documented.
Found while documenting the execution behaviour in #1094, where I first got this wrong in the opposite direction and claimed the dry run skipped the write.