diff --git a/.github/workflows/ci.yaml b/.github/workflows/ci.yaml index 26f492bcf10..23daee979f7 100644 --- a/.github/workflows/ci.yaml +++ b/.github/workflows/ci.yaml @@ -420,6 +420,10 @@ jobs: run: make test working-directory: ./test/running_modules + - name: test/multi_namespace + run: ./test.sh + working-directory: ./test/multi_namespace + - name: Test FFI in subdirectories run: make working-directory: ./test/subdir_ffi diff --git a/compiler-cli/src/publish.rs b/compiler-cli/src/publish.rs index 299b1b82190..2965f4bcf64 100644 --- a/compiler-cli/src/publish.rs +++ b/compiler-cli/src/publish.rs @@ -5,7 +5,7 @@ use gleam_core::{ build::{Codegen, Compile, Mode, Options, Package, Target}, config::{PackageConfig, SpdxLicense}, docs::DocContext, - error::SmallVersion, + error::{wrap, SmallVersion}, hex, paths::{self, ProjectPaths}, requirement::Requirement, @@ -56,6 +56,7 @@ impl PublishCommand { } = do_build_hex_tarball(&paths, &mut config)?; check_for_name_squatting(&compile_result)?; + check_for_multiple_top_level_modules(&compile_result, i_am_sure)?; // Build HTML documentation let docs_tarball = fs::create_tar_archive(docs::build_documentation( @@ -123,6 +124,50 @@ fn check_for_name_squatting(package: &Package) -> Result<(), Error> { Ok(()) } +fn check_for_multiple_top_level_modules(package: &Package, i_am_sure: bool) -> Result<(), Error> { + // Collect top-level module names + let mut top_level_module_names = package + .modules + .iter() + .filter_map(|module| module.name.split('/').next()) + .collect::>(); + + // Remove duplicates + top_level_module_names.sort_unstable(); + top_level_module_names.dedup(); + + // If more than one top-level module name is found, prompt for confirmation + if top_level_module_names.len() > 1 { + let text = wrap(&format!( + "Your package defines multiple top-level modules: {}. + +Defining multiple top-level modules can lead to namespace pollution \ +and potential conflicts for consumers. + +To fix this, move all your modules under a single top-level module of your choice. + +For example: + src/{1}.gleam + src/{1}/module1.gleam + src/{1}/module2.gleam", + top_level_module_names.join(", "), + package.config.name + )); + println!("{text}\n"); + + let should_publish = + i_am_sure || cli::confirm("\nDo you wish to continue publishing this package?")?; + println!(); + + if !should_publish { + println!("Not publishing."); + std::process::exit(0); + } + } + + Ok(()) +} + fn check_repo_url(config: &PackageConfig, i_am_sure: bool) -> Result { let Some(url) = config.repository.url() else { return Ok(true); diff --git a/test/multi_namespace/.gitignore b/test/multi_namespace/.gitignore new file mode 100644 index 00000000000..c1dc9b0dd00 --- /dev/null +++ b/test/multi_namespace/.gitignore @@ -0,0 +1,3 @@ +*.beam +*.ez +build diff --git a/test/multi_namespace/gleam.toml b/test/multi_namespace/gleam.toml new file mode 100644 index 00000000000..7e87bd8cedd --- /dev/null +++ b/test/multi_namespace/gleam.toml @@ -0,0 +1,4 @@ +name = "multi_namespace" +version = "1.0.0" +description = "Test project for multi namespace" +licences = ["Apache-2.0"] diff --git a/test/multi_namespace/manifest.toml b/test/multi_namespace/manifest.toml new file mode 100644 index 00000000000..c5d779a3f81 --- /dev/null +++ b/test/multi_namespace/manifest.toml @@ -0,0 +1,7 @@ +# This file was generated by Gleam +# You typically do not need to edit this file + +packages = [ +] + +[requirements] diff --git a/test/multi_namespace/src/multi_namespace.gleam b/test/multi_namespace/src/multi_namespace.gleam new file mode 100644 index 00000000000..e68bfe18658 --- /dev/null +++ b/test/multi_namespace/src/multi_namespace.gleam @@ -0,0 +1,3 @@ +pub fn main() { + "Hello from multi_namespace!" +} diff --git a/test/multi_namespace/src/second.gleam b/test/multi_namespace/src/second.gleam new file mode 100644 index 00000000000..d0737279e39 --- /dev/null +++ b/test/multi_namespace/src/second.gleam @@ -0,0 +1,3 @@ +pub fn main() { + "Hello from second!" +} diff --git a/test/multi_namespace/test.sh b/test/multi_namespace/test.sh new file mode 100755 index 00000000000..91560f63ce6 --- /dev/null +++ b/test/multi_namespace/test.sh @@ -0,0 +1,26 @@ +#!/bin/sh + +set -eu + +GLEAM_COMMAND=${GLEAM_COMMAND:-"cargo run --quiet --"} + +g() { + echo "Running: $GLEAM_COMMAND $@" + $GLEAM_COMMAND "$@" +} + +echo Resetting the build directory to get to a known state +rm -fr build + +echo Running publish should not publish anything +output=$(yes "n" | g publish) +if echo "$output" | grep -q "Your package defines multiple top-level modules"; then + echo "Publish was correctly prevented with warning" +else + echo "Expected publish to be aborted" + exit 1 +fi + +echo +echo Success! 💖 +echo