From 84b7504d0fdccc07b9f7412408c954ae07765ccb Mon Sep 17 00:00:00 2001 From: David Sherret Date: Wed, 24 Jul 2024 21:43:30 -0400 Subject: [PATCH] fix(workspaces/publish): include the license file from the workspace root if not in pkg (#24714) --- cli/tools/registry/mod.rs | 79 +++++++++++++------ cli/tools/registry/paths.rs | 4 + cli/tools/registry/tar.rs | 22 +++--- .../specs/publish/workspace/{foo => }/LICENSE | 0 tests/specs/publish/workspace/__test__.jsonc | 9 +++ tests/specs/publish/workspace/foo_dry_run.out | 9 +++ .../publish/workspace/workspace_dry_run.out | 17 ++++ 7 files changed, 108 insertions(+), 32 deletions(-) rename tests/specs/publish/workspace/{foo => }/LICENSE (100%) create mode 100644 tests/specs/publish/workspace/foo_dry_run.out create mode 100644 tests/specs/publish/workspace/workspace_dry_run.out diff --git a/cli/tools/registry/mod.rs b/cli/tools/registry/mod.rs index bf9ce85861..c9b742588b 100644 --- a/cli/tools/registry/mod.rs +++ b/cli/tools/registry/mod.rs @@ -4,6 +4,7 @@ use std::collections::HashMap; use std::collections::HashSet; use std::io::IsTerminal; use std::path::Path; +use std::path::PathBuf; use std::process::Stdio; use std::rc::Rc; use std::sync::Arc; @@ -13,6 +14,7 @@ use base64::Engine; use deno_ast::ModuleSpecifier; use deno_config::workspace::JsrPackageConfig; use deno_config::workspace::PackageJsonDepResolution; +use deno_config::workspace::Workspace; use deno_core::anyhow::bail; use deno_core::anyhow::Context; use deno_core::error::AnyError; @@ -448,7 +450,7 @@ impl PublishPreparer { move || { let root_specifier = ModuleSpecifier::from_directory_path(&root_dir).unwrap(); - let publish_paths = + let mut publish_paths = paths::collect_publish_paths(paths::CollectPublishPathsOptions { root_dir: &root_dir, cli_options: &cli_options, @@ -464,13 +466,28 @@ impl PublishPreparer { ); if !has_license_file(publish_paths.iter().map(|p| &p.specifier)) { - diagnostics_collector.push(PublishDiagnostic::MissingLicense { - expected_path: root_dir.join("LICENSE"), - }); + if let Some(license_path) = + resolve_license_file(&root_dir, cli_options.workspace()) + { + // force including the license file from the package or workspace root + publish_paths.push(CollectedPublishPath { + specifier: ModuleSpecifier::from_file_path(&license_path) + .unwrap(), + relative_path: "LICENSE".to_string(), + maybe_content: Some(std::fs::read(&license_path).with_context( + || format!("failed reading '{}'.", license_path.display()), + )?), + path: license_path, + }); + } else { + diagnostics_collector.push(PublishDiagnostic::MissingLicense { + expected_path: root_dir.join("LICENSE"), + }); + } } tar::create_gzipped_tarball( - &publish_paths, + publish_paths, LazyGraphSourceParser::new(&source_cache, &graph), &diagnostics_collector, &unfurler, @@ -1194,31 +1211,49 @@ async fn check_if_git_repo_dirty(cwd: &Path) -> Option { } } +static SUPPORTED_LICENSE_FILE_NAMES: [&str; 6] = [ + "LICENSE", + "LICENSE.md", + "LICENSE.txt", + "LICENCE", + "LICENCE.md", + "LICENCE.txt", +]; + +fn resolve_license_file( + pkg_root_dir: &Path, + workspace: &Workspace, +) -> Option { + let workspace_root_dir = workspace.root_dir_path(); + let mut dirs = Vec::with_capacity(2); + dirs.push(pkg_root_dir); + if workspace_root_dir != pkg_root_dir { + dirs.push(&workspace_root_dir); + } + for dir in dirs { + for file_name in &SUPPORTED_LICENSE_FILE_NAMES { + let file_path = dir.join(file_name); + if file_path.exists() { + return Some(file_path); + } + } + } + None +} + fn has_license_file<'a>( mut specifiers: impl Iterator, ) -> bool { - let allowed_license_files = { - let files = HashSet::from([ - "license", - "license.md", - "license.txt", - "licence", - "licence.md", - "licence.txt", - ]); - if cfg!(debug_assertions) { - for file in &files { - assert_eq!(*file, file.to_lowercase()); - } - } - files - }; + let supported_license_files = SUPPORTED_LICENSE_FILE_NAMES + .iter() + .map(|s| s.to_lowercase()) + .collect::>(); specifiers.any(|specifier| { specifier .path() .rsplit_once('/') .map(|(_, file)| { - allowed_license_files.contains(file.to_lowercase().as_str()) + supported_license_files.contains(file.to_lowercase().as_str()) }) .unwrap_or(false) }) diff --git a/cli/tools/registry/paths.rs b/cli/tools/registry/paths.rs index 1fe8830dd5..5943e0cbb0 100644 --- a/cli/tools/registry/paths.rs +++ b/cli/tools/registry/paths.rs @@ -214,7 +214,10 @@ pub enum PackagePathValidationError { pub struct CollectedPublishPath { pub specifier: ModuleSpecifier, pub path: PathBuf, + /// Relative path to use in the tarball. pub relative_path: String, + /// Specify the contents for any injected paths. + pub maybe_content: Option>, } pub struct CollectPublishPathsOptions<'a> { @@ -307,6 +310,7 @@ pub fn collect_publish_paths( specifier, path, relative_path, + maybe_content: None, }); } diff --git a/cli/tools/registry/tar.rs b/cli/tools/registry/tar.rs index f98d4b09c9..27e4165378 100644 --- a/cli/tools/registry/tar.rs +++ b/cli/tools/registry/tar.rs @@ -34,7 +34,7 @@ pub struct PublishableTarball { } pub fn create_gzipped_tarball( - publish_paths: &[CollectedPublishPath], + publish_paths: Vec, source_parser: LazyGraphSourceParser, diagnostics_collector: &PublishDiagnosticsCollector, unfurler: &SpecifierUnfurler, @@ -45,15 +45,17 @@ pub fn create_gzipped_tarball( for path in publish_paths { let path_str = &path.relative_path; let specifier = &path.specifier; - let path = &path.path; - let content = resolve_content_maybe_unfurling( - path, - specifier, - unfurler, - source_parser, - diagnostics_collector, - )?; + let content = match path.maybe_content { + Some(content) => content.clone(), + None => resolve_content_maybe_unfurling( + &path.path, + specifier, + unfurler, + source_parser, + diagnostics_collector, + )?, + }; files.push(PublishableTarballFile { path_str: path_str.clone(), @@ -65,7 +67,7 @@ pub fn create_gzipped_tarball( tar .add_file(format!(".{}", path_str), &content) .with_context(|| { - format!("Unable to add file to tarball '{}'", path.display()) + format!("Unable to add file to tarball '{}'", path.path.display()) })?; } diff --git a/tests/specs/publish/workspace/foo/LICENSE b/tests/specs/publish/workspace/LICENSE similarity index 100% rename from tests/specs/publish/workspace/foo/LICENSE rename to tests/specs/publish/workspace/LICENSE diff --git a/tests/specs/publish/workspace/__test__.jsonc b/tests/specs/publish/workspace/__test__.jsonc index 706b08ccd5..0ac5c27185 100644 --- a/tests/specs/publish/workspace/__test__.jsonc +++ b/tests/specs/publish/workspace/__test__.jsonc @@ -4,6 +4,15 @@ "args": "publish --token 'sadfasdf'", "output": "workspace.out" }, + "workspace_dry_run": { + "args": "publish --token 'sadfasdf' --dry-run", + "output": "workspace_dry_run.out" + }, + "individual_dry_run": { + "cwd": "./foo", + "args": "publish --token 'sadfasdf' --dry-run", + "output": "foo_dry_run.out" + }, "individual": { "cwd": "./bar", "args": "publish --token 'sadfasdf'", diff --git a/tests/specs/publish/workspace/foo_dry_run.out b/tests/specs/publish/workspace/foo_dry_run.out new file mode 100644 index 0000000000..20759c180f --- /dev/null +++ b/tests/specs/publish/workspace/foo_dry_run.out @@ -0,0 +1,9 @@ +Check file:///[WILDLINE]/foo/mod.ts +Checking for slow types in the public API... +Check file:///[WILDLINE]/foo/mod.ts +Simulating publish of @foo/foo@1.0.0 with files: +[# notice how this line is including the LICENSE from the root directory] + file:///[WILDLINE]/workspace/LICENSE (0B) + file:///[WILDLINE]/workspace/foo/deno.json (135B) + file:///[WILDLINE]/workspace/foo/mod.ts (118B) +Warning Aborting due to --dry-run diff --git a/tests/specs/publish/workspace/workspace_dry_run.out b/tests/specs/publish/workspace/workspace_dry_run.out new file mode 100644 index 0000000000..dd8eff0ff8 --- /dev/null +++ b/tests/specs/publish/workspace/workspace_dry_run.out @@ -0,0 +1,17 @@ +Publishing a workspace... +Check file:///[WILDLINE]/workspace/bar/mod.ts +Check file:///[WILDLINE]/workspace/foo/mod.ts +Checking for slow types in the public API... +Check file:///[WILDLINE]/workspace/bar/mod.ts +Check file:///[WILDLINE]/workspace/foo/mod.ts +[UNORDERED_START] +Simulating publish of @foo/foo@1.0.0 with files: + file:///[WILDLINE]/workspace/LICENSE (0B) + file:///[WILDLINE]/workspace/foo/deno.json (135B) + file:///[WILDLINE]/workspace/foo/mod.ts (118B) +Simulating publish of @foo/bar@1.0.0 with files: + file:///[WILDLINE]/workspace/bar/LICENSE (0B) + file:///[WILDLINE]/workspace/bar/deno.json (87B) + file:///[WILDLINE]/workspace/bar/mod.ts (70B) +[UNORDERED_END] +Warning Aborting due to --dry-run