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
42 changes: 42 additions & 0 deletions src/commands.rs
Original file line number Diff line number Diff line change
Expand Up @@ -141,6 +141,8 @@ impl CommandHandler {
.await?;
println!("{}", "✅ Git hooks generated successfully!".green());
}

self.print_ruby_next_steps();
}
Ok(())
}
Expand Down Expand Up @@ -265,6 +267,8 @@ impl CommandHandler {
.await?;
println!("{}", "✅ Git hooks generated successfully!".green());
}

self.print_node_next_steps();
}
Ok(())
}
Expand Down Expand Up @@ -619,6 +623,7 @@ impl CommandHandler {
"{}",
"✅ Ruby configuration files generated successfully!".green()
);
self.print_ruby_next_steps();
}
}
ProjectType::Python => {
Expand Down Expand Up @@ -659,6 +664,7 @@ impl CommandHandler {
"{}",
"✅ Node.js configuration files generated successfully!".green()
);
self.print_node_next_steps();
}
}
ProjectType::Go => {
Expand Down Expand Up @@ -801,6 +807,42 @@ impl CommandHandler {
Ok(())
}

/// Tell the user which commands install the dependencies we just injected.
///
/// Initium writes `package.json` (and, for Ruby, expects Gemfile gems) but
/// never runs the installers itself, so the generated config is inert until
/// the user installs the tooling. Skipped on dry runs.
fn print_ruby_next_steps(&self) {
if self.dry_run {
return;
}
println!();
println!(
"{}",
"📦 Next steps — install the tooling this config expects:".blue()
);
println!(" • npm install # Prettier + @prettier/plugin-ruby");
println!(
" • {}",
"bundle add syntax_tree prettier_print --group development,test".yellow()
);
println!(" (required by @prettier/plugin-ruby to parse Ruby)");
println!(" • bundle install");
println!(" Then run {} to format.", "just fmt".green());
}

fn print_node_next_steps(&self) {
if self.dry_run {
return;
}
println!();
println!(
"{}",
"📦 Next steps — install the tooling this config expects:".blue()
);
println!(" • npm install");
}

pub fn handle_list(&self) {
println!("{}", "📋 Available configuration files:".blue());
println!(" • .editorconfig");
Expand Down
84 changes: 53 additions & 31 deletions src/config.rs
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,8 @@ pub struct PrettierConfig {
pub trailing_comma: String,
pub print_width: u8,
pub plugins: Option<Vec<String>>,
/// Emits `rubySingleQuote` when set. Only meaningful with `@prettier/plugin-ruby`.
pub ruby_single_quote: Option<bool>,
}

#[derive(Debug, Clone, Serialize, Deserialize)]
Expand Down Expand Up @@ -183,14 +185,26 @@ impl Default for PrettierConfig {
trailing_comma: "es5".to_string(),
print_width: 80,
plugins: None,
ruby_single_quote: None,
}
}
}

impl PrettierConfig {
fn with_ruby_plugin(mut self) -> Self {
self.single_quote = false;
self.plugins = Some(vec!["@prettier/plugin-ruby".to_string()]);
/// Configure Prettier as the Ruby formatting source of truth.
///
/// `@prettier/plugin-ruby` (npm) drives Ruby style; RuboCop cops that fight
/// it are disabled in `.rubocop.yml`. Both JS and Ruby use single quotes.
/// When `erb` is set, ERB templates are formatted via
/// `@4az/prettier-plugin-html-erb`.
fn with_ruby_plugin(mut self, erb: bool) -> Self {
self.single_quote = true;
self.ruby_single_quote = Some(true);
let mut plugins = vec!["@prettier/plugin-ruby".to_string()];
if erb {
plugins.push("@4az/prettier-plugin-html-erb".to_string());
}
self.plugins = Some(plugins);
self
}

Expand All @@ -203,6 +217,7 @@ impl PrettierConfig {
trailing_comma: "es5".to_string(),
print_width: 80,
plugins: None,
ruby_single_quote: None,
},
"airbnb" => Self {
semi: true,
Expand All @@ -211,8 +226,11 @@ impl PrettierConfig {
trailing_comma: "es5".to_string(),
print_width: 100,
plugins: None,
ruby_single_quote: None,
},
"rails" | "sinatra" | "gem" | "ruby" => Self::default().with_ruby_plugin(),
// Rails renders ERB views, so it also gets the HTML+ERB plugin.
"rails" => Self::default().with_ruby_plugin(true),
"sinatra" | "gem" | "ruby" => Self::default().with_ruby_plugin(false),
_ => Self::default(),
}
}
Expand All @@ -237,24 +255,41 @@ impl fmt::Display for PrettierConfig {
write!(f, "]")?;
}

if let Some(ruby_single_quote) = self.ruby_single_quote {
write!(f, r#", "rubySingleQuote": {}"#, ruby_single_quote)?;
}

write!(f, "}}")
}
}

/// Pinned npm version for `@prettier/plugin-ruby` (the Ruby formatter plugin).
const PRETTIER_PLUGIN_RUBY_VERSION: &str = "^4.0.4";
/// Pinned npm version for `@4az/prettier-plugin-html-erb` (ERB view formatter).
const PRETTIER_PLUGIN_HTML_ERB_VERSION: &str = "^0.0.7";

/// Base Prettier dev-dependencies shared by every Ruby package.json.
///
/// Uses the published, scoped `@prettier/plugin-ruby` package — NOT the
/// unscoped `prettier-plugin-ruby` GitHub tarball, which does not match the
/// `.prettierrc` plugin id and leaves Prettier unable to format Ruby.
fn ruby_prettier_deps() -> HashMap<String, String> {
let mut deps = HashMap::new();
deps.insert("prettier".to_string(), "^3.0.0".to_string());
deps.insert(
"@prettier/plugin-ruby".to_string(),
PRETTIER_PLUGIN_RUBY_VERSION.to_string(),
);
deps
}

impl Default for PackageJson {
fn default() -> Self {
let mut dev_dependencies = HashMap::new();
dev_dependencies.insert("prettier".to_string(), "^3.0.0".to_string());
dev_dependencies.insert(
"prettier-plugin-ruby".to_string(),
"github:prettier/plugin-ruby".to_string(),
);

Self {
name: "project".to_string(),
version: "0.1.0".to_string(),
description: "A Ruby project".to_string(),
dev_dependencies,
dev_dependencies: ruby_prettier_deps(),
}
}
}
Expand All @@ -267,11 +302,11 @@ impl PackageJson {
version: "0.1.0".to_string(),
description: "A Rails web application".to_string(),
dev_dependencies: {
let mut deps = HashMap::new();
deps.insert("prettier".to_string(), "^3.0.0".to_string());
let mut deps = ruby_prettier_deps();
// Rails renders ERB views; format them with the HTML+ERB plugin.
deps.insert(
"prettier-plugin-ruby".to_string(),
"github:prettier/plugin-ruby".to_string(),
"@4az/prettier-plugin-html-erb".to_string(),
PRETTIER_PLUGIN_HTML_ERB_VERSION.to_string(),
);
deps.insert("eslint".to_string(), "^8.0.0".to_string());
deps
Expand All @@ -281,27 +316,14 @@ impl PackageJson {
name: "sinatra-project".to_string(),
version: "0.1.0".to_string(),
description: "A Sinatra web application".to_string(),
dev_dependencies: {
let mut deps = HashMap::new();
deps.insert("prettier".to_string(), "^3.0.0".to_string());
deps.insert(
"prettier-plugin-ruby".to_string(),
"github:prettier/plugin-ruby".to_string(),
);
deps
},
dev_dependencies: ruby_prettier_deps(),
},
"gem" => Self {
name: "ruby-gem".to_string(),
version: "0.1.0".to_string(),
description: "A Ruby gem".to_string(),
dev_dependencies: {
let mut deps = HashMap::new();
deps.insert("prettier".to_string(), "^3.0.0".to_string());
deps.insert(
"prettier-plugin-ruby".to_string(),
"github:prettier/plugin-ruby".to_string(),
);
let mut deps = ruby_prettier_deps();
deps.insert("rspec".to_string(), "^3.12.0".to_string());
deps
},
Expand Down
21 changes: 19 additions & 2 deletions src/generators/basic.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
use crate::config::{EditorConfig, PrettierConfig};
use crate::error::InitiumError;

/// Paths Prettier should skip. Ruby/Rails projects carry framework-specific
/// build and vendor directories, so they get a richer ignore list than the
/// generic default.
fn prettier_ignore_content(template: &str) -> &'static str {
match template {
"ruby" | "rails" | "sinatra" | "gem" => {
"node_modules\nvendor\ntmp\nlog\nstorage\ncoverage\npublic/assets\npublic/packs\n.gitignore\n*.min.js\n*.min.css\n"
}
_ => "node_modules\ndist\nbuild\ncoverage\n*.min.js\n*.min.css\n",
}
}

impl super::ConfigGenerator {
pub async fn generate_basic(&self, fail_on_exists: bool) -> Result<(), InitiumError> {
self.generate_basic_with_template(fail_on_exists, "default")
Expand All @@ -19,8 +31,13 @@ impl super::ConfigGenerator {
let prettier = PrettierConfig::from_template(template);
self.emit_file(".prettierrc", &prettier.to_string(), fail_on_exists, false)
.await?;
self.emit_file(".prettierignore", "", fail_on_exists, false)
.await?;
self.emit_file(
".prettierignore",
prettier_ignore_content(template),
fail_on_exists,
false,
)
.await?;

let justfile_content = r#"# Basic project justfile
# Add your project-specific commands here
Expand Down
Loading
Loading