diff --git a/CHANGELOG.md b/CHANGELOG.md index b015b5ba0a..5d4598d928 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,10 @@ The custom build command can be set in `dfx.json` the same way it is set for `cu } ``` +### fix: Diagnose duplicate assets and display upgrade steps + +If `dfx deploy` detects duplicate assets in the dist/ and frontend assets/ directories, it will now suggest upgrade steps. + ### fix: motoko canisters can import other canisters with service constructor After specific canister builder output wasm and candid file, `dfx` will do some post processing on the candid file. diff --git a/e2e/tests-dfx/error_diagnosis.bash b/e2e/tests-dfx/error_diagnosis.bash index 1554ecf468..fa774ac1cf 100644 --- a/e2e/tests-dfx/error_diagnosis.bash +++ b/e2e/tests-dfx/error_diagnosis.bash @@ -12,6 +12,20 @@ teardown() { standard_teardown } +@test "Duplicate assets in dist/ from src/" { + dfx_new_frontend hello + install_asset greet + dfx_start + assert_command dfx deploy + + # simulate previous deploy with CopyPlugin step + cp src/hello_frontend/assets/* dist/hello_frontend/ + + assert_command_fail dfx deploy + assert_contains "Remove the CopyPlugin step from webpack.config.js" + assert_contains "Delete all files from the dist/ directory" +} + @test "HTTP 403 has a full diagnosis" { dfx_new hello install_asset greet diff --git a/src/dfx/src/lib/diagnosis.rs b/src/dfx/src/lib/diagnosis.rs index 3ada7bfaef..61e8ff6c4b 100644 --- a/src/dfx/src/lib/diagnosis.rs +++ b/src/dfx/src/lib/diagnosis.rs @@ -2,6 +2,9 @@ use crate::lib::error_code; use anyhow::Error as AnyhowError; use ic_agent::agent::{RejectCode, RejectResponse}; use ic_agent::AgentError; +use ic_asset::error::{GatherAssetDescriptorsError, SyncError, UploadContentError}; +use regex::Regex; +use std::path::Path; use thiserror::Error as ThisError; use super::environment::Environment; @@ -49,6 +52,12 @@ pub fn diagnose(_env: &dyn Environment, err: &AnyhowError) -> Diagnosis { } } + if let Some(sync_error) = err.downcast_ref::() { + if duplicate_asset_key_dist_and_src(sync_error) { + return diagnose_duplicate_asset_key_dist_and_src(); + } + } + NULL_DIAGNOSIS } @@ -90,3 +99,49 @@ The most common way this error is solved is by running 'dfx canister update-sett Some(action_suggestion.to_string()), ) } + +fn duplicate_asset_key_dist_and_src(sync_error: &SyncError) -> bool { + fn is_src_to_dist(path0: &Path, path1: &Path) -> bool { + // .../dist//... and .../src//assets/... + let path0 = path0.to_string_lossy(); + let path1 = path1.to_string_lossy(); + let re = Regex::new(r"(?P.*)/dist/(?P[^/]*)/(?P.*)").unwrap(); + + if let Some(caps) = re.captures(&path0) { + let project_dir = caps["project_dir"].to_string(); + let canister = caps["canister"].to_string(); + let rest = caps["rest"].to_string(); + let transformed = format!("{}/src/{}/assets/{}", project_dir, canister, rest); + return transformed == path1; + } + false + } + matches!(sync_error, + SyncError::UploadContentFailed( + UploadContentError::GatherAssetDescriptorsFailed( + GatherAssetDescriptorsError::DuplicateAssetKey(_key, path0, path1))) + if is_src_to_dist(path0, path1) + ) +} + +fn diagnose_duplicate_asset_key_dist_and_src() -> Diagnosis { + let explanation = "An asset key was found in both the dist and src directories. +One or both of the following are a likely explanation: + - webpack.config.js is configured to copy assets from the src directory to the dist/ directory. + - there are leftover files in the dist/ directory from a previous build."; + let suggestion = r#"Perform the following steps: + 1. Remove the CopyPlugin step from webpack.config.js. It looks like this: + new CopyPlugin({ + patterns: [ + { + from: path.join(__dirname, "src", frontendDirectory, "assets"), + to: path.join(__dirname, "dist", frontendDirectory), + }, + ], + }), + 2. Delete all files from the dist/ directory." + +See also release notes: https://forum.dfinity.org/t/dfx-0-11-0-is-promoted-with-breaking-changes/14327"#; + + (Some(explanation.to_string()), Some(suggestion.to_string())) +}