Skip to content
Open
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
55 changes: 53 additions & 2 deletions rewatch/src/build.rs
Original file line number Diff line number Diff line change
Expand Up @@ -436,8 +436,21 @@ pub fn incremental_build(
}

fn log_config_warnings(build_state: &BuildCommandState) {
build_state.packages.iter().for_each(|(_, package)| {
// Only warn for local dependencies, not external packages
let mut packages: Vec<_> = build_state.packages.values().collect();
packages.sort_by(|a, b| a.name.cmp(&b.name));
packages.iter().for_each(|package| {
let deprecations = package.config.get_deprecations();
if !deprecations.is_empty() {
// External consumers can't fix deprecations themselves, so we
// point them at the package's issue tracker when we can find one.
let issue_tracker_url = if package.is_local_dep {
None
} else {
crate::build::packages::read_issue_tracker_url(&package.path)
};
log_deprecations(&package.name, deprecations, issue_tracker_url.as_deref());
}

if package.is_local_dep {
package
.config
Expand All @@ -454,6 +467,44 @@ fn log_config_warnings(build_state: &BuildCommandState) {
});
}

fn log_deprecations(
package_name: &str,
deprecations: &[crate::config::DeprecationWarning],
issue_tracker_url: Option<&str>,
) {
let mut message = format!(
"Package '{package_name}' uses deprecated config (support will be removed in a future version):"
);
for deprecation in deprecations {
let line = match deprecation {
crate::config::DeprecationWarning::BsconfigJson => {
" - filename 'bsconfig.json' — rename to 'rescript.json'"
}
crate::config::DeprecationWarning::BsDependencies => {
" - field 'bs-dependencies' — use 'dependencies' instead"
}
crate::config::DeprecationWarning::BsDevDependencies => {
" - field 'bs-dev-dependencies' — use 'dev-dependencies' instead"
}
crate::config::DeprecationWarning::BscFlags => {
" - field 'bsc-flags' — use 'compiler-flags' instead"
}
crate::config::DeprecationWarning::CjsModule => {
" - module 'cjs' in package-specs — use 'commonjs' instead"
}
crate::config::DeprecationWarning::Es6Module => {
" - module 'es6' in package-specs — use 'esmodule' instead"
}
};
message.push('\n');
message.push_str(line);
}
if let Some(url) = issue_tracker_url {
message.push_str(&format!("\nPlease report this to the package maintainer: {url}"));
}
eprintln!("\n{}", style(message).yellow());
}

fn log_unsupported_config_field(package_name: &str, field_name: &str) {
let warning = format!(
"The field '{field_name}' found in the package config of '{package_name}' is not supported by ReScript 12's new build system."
Expand Down
123 changes: 121 additions & 2 deletions rewatch/src/build/packages.rs
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,13 @@ fn get_source_dirs(source: config::Source, sub_path: Option<PathBuf>) -> AHashSe

pub fn read_config(package_dir: &Path) -> Result<Config> {
let rescript_json_path = package_dir.join("rescript.json");
Config::new(&rescript_json_path)
let bsconfig_json_path = package_dir.join("bsconfig.json");

if rescript_json_path.exists() {
Config::new(&rescript_json_path)
} else {
Config::new(&bsconfig_json_path)
Comment thread
cknitt marked this conversation as resolved.
}
}

pub fn read_dependency(
Expand Down Expand Up @@ -446,12 +452,52 @@ pub fn read_package_name(package_dir: &Path) -> Result<String> {
return Ok(name);
}

if let Some(name) = read_name("bsconfig.json")? {
return Ok(name);
}

Err(anyhow!(
"No name field found in package.json or rescript.json in {}",
package_dir.to_string_lossy()
))
}

/// Looks up the best-effort issue tracker URL for a package by reading its
/// package.json, preferring `bugs.url`, then deriving from `repository`.
/// Returns None if no tracker could be inferred.
pub fn read_issue_tracker_url(package_dir: &Path) -> Option<String> {
let contents = fs::read_to_string(package_dir.join("package.json")).ok()?;
let json: serde_json::Value = serde_json::from_str(&contents).ok()?;

let extract_url = |v: &serde_json::Value| -> Option<String> {
match v {
serde_json::Value::String(s) => Some(s.to_owned()),
serde_json::Value::Object(o) => o.get("url").and_then(|u| u.as_str()).map(String::from),
_ => None,
}
};

if let Some(bugs_url) = json.get("bugs").and_then(extract_url) {
return Some(bugs_url);
}

json.get("repository")
.and_then(extract_url)
.map(|repo| issues_url_from_repository(&repo))
}

fn issues_url_from_repository(repo: &str) -> String {
let cleaned = repo.trim_start_matches("git+").trim_end_matches(".git");

// npm shorthand (no scheme, no user@): treat as github-style "owner/repo"
if !cleaned.contains("://") && !cleaned.contains('@') {
let path = cleaned.trim_start_matches("github:");
return format!("https://github.com/{path}/issues");
}

format!("{cleaned}/issues")
}

fn make_package(
config: config::Config,
package_path: &Path,
Expand Down Expand Up @@ -1042,7 +1088,7 @@ pub fn validate_packages_dependencies(packages: &AHashMap<String, Package>) -> b
mod test {
use crate::config;

use super::{Namespace, Package, read_package_name};
use super::{Namespace, Package, read_issue_tracker_url, read_package_name};
use ahash::{AHashMap, AHashSet};
use std::fs;
use std::path::PathBuf;
Expand Down Expand Up @@ -1176,4 +1222,77 @@ mod test {
)
);
}

fn write_pkg_json(dir: &std::path::Path, body: &str) {
fs::write(dir.join("package.json"), body).expect("package.json should be written");
}

#[test]
fn issue_tracker_url_prefers_bugs_url_string() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(
temp_dir.path(),
r#"{"name":"x","bugs":"https://example.com/issues"}"#,
);
assert_eq!(
read_issue_tracker_url(temp_dir.path()),
Some("https://example.com/issues".to_string())
);
}

#[test]
fn issue_tracker_url_prefers_bugs_object_url() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(
temp_dir.path(),
r#"{"name":"x","bugs":{"url":"https://example.com/report"}}"#,
);
assert_eq!(
read_issue_tracker_url(temp_dir.path()),
Some("https://example.com/report".to_string())
);
}

#[test]
fn issue_tracker_url_derives_from_repository_git_url() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(
temp_dir.path(),
r#"{"name":"x","repository":"git+https://github.com/owner/repo.git"}"#,
);
assert_eq!(
read_issue_tracker_url(temp_dir.path()),
Some("https://github.com/owner/repo/issues".to_string())
);
}

#[test]
fn issue_tracker_url_derives_from_repository_object() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(
temp_dir.path(),
r#"{"name":"x","repository":{"type":"git","url":"https://github.com/owner/repo.git"}}"#,
);
assert_eq!(
read_issue_tracker_url(temp_dir.path()),
Some("https://github.com/owner/repo/issues".to_string())
);
}

#[test]
fn issue_tracker_url_handles_shorthand() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(temp_dir.path(), r#"{"name":"x","repository":"owner/repo"}"#);
assert_eq!(
read_issue_tracker_url(temp_dir.path()),
Some("https://github.com/owner/repo/issues".to_string())
);
}

#[test]
fn issue_tracker_url_returns_none_without_hints() {
let temp_dir = TempDir::new().unwrap();
write_pkg_json(temp_dir.path(), r#"{"name":"x"}"#);
assert_eq!(read_issue_tracker_url(temp_dir.path()), None);
}
}
Loading
Loading