Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,8 @@ ferrflow migrate --from semantic-release

It maps `tagFormat`, `branches`, and the common plugins (`changelog`, `exec`, `github`/`gitlab`) to their FerrFlow equivalents, and prints a summary of what mapped, what was ignored, and what needs manual review. Anything without a FerrFlow equivalent is surfaced, never guessed. JSON `.releaserc` is supported today; YAML `.releaserc` and JS `release.config.js` are reported as unsupported rather than mis-parsed.

`ferrflow migrate --from release-please` also maps `extra-files` (`generic` markers to a `txt` selector, `toml` on `Cargo.toml` and `json` on `$.version` to their formats), `bump-minor-pre-major` and `bump-patch-for-minor-pre-major` to `preMajor` (`breaking: minor`, `feat: patch`), and writes `autoMergeReleases: false` since release-please never merges its release PR. Lockfile updates are enabled when a versioned manifest has one. Entries it cannot express are listed under "Review manually". Only non-default keys are written.

### JSON Schema

Add `$schema` to get autocompletion and validation in VS Code, WebStorm, and any JSON-aware editor:
Expand Down
16 changes: 13 additions & 3 deletions src/config/migrate.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ use super::types::{
use super::workspace::WorkspaceConfig;

mod changesets;
mod prune;
mod release_please;
mod standard_version;

Expand Down Expand Up @@ -96,9 +97,10 @@ pub fn migrate(from: Option<Source>, dry_run: bool) -> Result<()> {
}

fn emit(migration: &Migration, dir: &Path, dry_run: bool) -> Result<(String, String)> {
let handler = format_handler(ConfigFileFormat::Json);
let content = handler.serialize(&migration.config)?;
let filename = handler.filename().to_string();
let filename = format_handler(ConfigFileFormat::Json)
.filename()
.to_string();
let content = prune::to_pretty_json(&migration.config)?;
if !dry_run {
std::fs::write(dir.join(&filename), &content)?;
}
Expand Down Expand Up @@ -377,9 +379,17 @@ pub(super) fn default_package_name() -> String {
.ok()
.and_then(|s| serde_json::from_str::<serde_json::Value>(&s).ok())
.and_then(|v| v.get("name").and_then(|n| n.as_str()).map(str::to_string))
.or_else(current_dir_name)
.unwrap_or_else(|| "app".to_string())
}

fn current_dir_name() -> Option<String> {
std::env::current_dir()
.ok()?
.file_name()
.map(|n| n.to_string_lossy().into_owned())
}

fn convert_tag_format(tag_format: &str) -> String {
tag_format.replace("${version}", "{version}")
}
Expand Down
37 changes: 37 additions & 0 deletions src/config/migrate/prune.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
use anyhow::Result;
use serde_json::{Map, Value};

use crate::config::Config;
use crate::config::format::to_camel_case_keys;
use crate::config::package::PackageConfig;
use crate::config::workspace::WorkspaceConfig;

const ALWAYS_WRITTEN_PACKAGE_KEYS: &[&str] = &["name", "path"];

pub(super) fn to_pretty_json(config: &Config) -> Result<String> {
let mut root = serde_json::to_value(config)?;
if let Value::Object(map) = &mut root {
strip_defaults(map, &serde_json::to_value(Config::default())?, &[]);
if let Some(Value::Object(workspace)) = map.get_mut("workspace") {
strip_defaults(
workspace,
&serde_json::to_value(WorkspaceConfig::default())?,
&[],
);
}
map.retain(|_, v| v.as_object().is_none_or(|o| !o.is_empty()));
if let Some(Value::Array(packages)) = map.get_mut("package") {
let defaults = serde_json::to_value(PackageConfig::default())?;
for package in packages.iter_mut().filter_map(Value::as_object_mut) {
strip_defaults(package, &defaults, ALWAYS_WRITTEN_PACKAGE_KEYS);
}
}
}
let mut out = serde_json::to_string_pretty(&to_camel_case_keys(root))?;
out.push('\n');
Ok(out)
}

fn strip_defaults(map: &mut Map<String, Value>, defaults: &Value, keep: &[&str]) {
map.retain(|key, value| keep.contains(&key.as_str()) || defaults.get(key) != Some(value));
}
Loading
Loading