From a3ddec7e885136823eb7544a552a261f714d52ae Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Mon, 2 Dec 2024 11:01:55 +0800 Subject: [PATCH 1/6] feat(copy): support advanced copy configuration with custom target paths - Add CopyConfig enum to support both basic and advanced copy modes - Basic mode: maintains backward compatibility with string[] format - Advanced mode: supports {from: string, to: string} format for custom paths - Update copy plugin to handle both configuration formats - Ensure target directories are created automatically Example config: { 'copy': [ 'public', // basic mode { 'from': 'assets', 'to': 'static' } // advanced mode ] } --- crates/mako/src/config.rs | 9 ++++++++- crates/mako/src/plugins/copy.rs | 34 +++++++++++++++++++++++++++------ 2 files changed, 36 insertions(+), 7 deletions(-) diff --git a/crates/mako/src/config.rs b/crates/mako/src/config.rs index 1dbc8c3ef..54e0500ab 100644 --- a/crates/mako/src/config.rs +++ b/crates/mako/src/config.rs @@ -123,6 +123,13 @@ pub enum Platform { Node, } +#[derive(Deserialize, Serialize, Debug)] +#[serde(untagged)] +pub enum CopyConfig { + Basic(String), + Advanced { from: String, to: String }, +} + #[derive(Deserialize, Serialize, Debug)] #[serde(rename_all = "camelCase")] pub struct Config { @@ -137,7 +144,7 @@ pub struct Config { pub devtool: Option, pub externals: HashMap, pub providers: Providers, - pub copy: Vec, + pub copy: Vec, pub public_path: String, pub inline_limit: usize, pub inline_excludes_extensions: Vec, diff --git a/crates/mako/src/plugins/copy.rs b/crates/mako/src/plugins/copy.rs index e62b3195d..d0a8415d7 100644 --- a/crates/mako/src/plugins/copy.rs +++ b/crates/mako/src/plugins/copy.rs @@ -1,3 +1,4 @@ +use std::fs; use std::path::Path; use std::sync::Arc; @@ -11,6 +12,7 @@ use tracing::debug; use crate::ast::file::win_path; use crate::compiler::Context; +use crate::config::CopyConfig; use crate::plugin::Plugin; use crate::stats::StatsJsonMap; use crate::utils::tokio_runtime; @@ -29,8 +31,12 @@ impl CopyPlugin { notify::Config::default(), ) .unwrap(); - for src in context.config.copy.iter() { - let src = context.root.join(src); + for config in context.config.copy.iter() { + let src = match config { + CopyConfig::Basic(src) => context.root.join(src), + CopyConfig::Advanced { from, .. } => context.root.join(from), + }; + if src.exists() { debug!("watch {:?}", src); let mode = if src.is_dir() { @@ -62,10 +68,26 @@ impl CopyPlugin { fn copy(context: &Arc) -> Result<()> { debug!("copy"); let dest = context.config.output.path.as_path(); - for src in context.config.copy.iter() { - let src = context.root.join(src); - debug!("copy {:?} to {:?}", src, dest); - copy(src.as_path(), dest)?; + for config in context.config.copy.iter() { + match config { + CopyConfig::Basic(src) => { + let src = context.root.join(src); + debug!("copy {:?} to {:?}", src, dest); + copy(&src, dest)?; + } + + CopyConfig::Advanced { from, to } => { + let src = context.root.join(from); + let target = dest.join(to.trim_start_matches("/")); + + if !target.exists() { + fs::create_dir_all(&target)?; + } + + debug!("copy {:?} to {:?}", src, target); + copy(&src, &target)?; + } + } } Ok(()) } From 464b49c73c43b672cdd16830d468acbb67e68b10 Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Mon, 2 Dec 2024 21:36:18 +0800 Subject: [PATCH 2/6] fix(copy): prevent path traversal in copy plugin Add path canonicalization and validation to ensure target paths remain within the destination directory --- crates/mako/src/plugins/copy.rs | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/crates/mako/src/plugins/copy.rs b/crates/mako/src/plugins/copy.rs index d0a8415d7..4c90312ce 100644 --- a/crates/mako/src/plugins/copy.rs +++ b/crates/mako/src/plugins/copy.rs @@ -2,7 +2,7 @@ use std::fs; use std::path::Path; use std::sync::Arc; -use anyhow::Result; +use anyhow::{anyhow, Result}; use fs_extra; use glob::glob; use notify::event::{CreateKind, DataChange, ModifyKind, RenameMode}; @@ -80,6 +80,12 @@ impl CopyPlugin { let src = context.root.join(from); let target = dest.join(to.trim_start_matches("/")); + let target = target.canonicalize()?; + let dest_canonical = dest.canonicalize()?; + if !target.starts_with(&dest_canonical) { + return Err(anyhow!("Invalid target path: {:?}", target)); + } + if !target.exists() { fs::create_dir_all(&target)?; } From ffc0081996abeca01e7b37eec9d9884388856126 Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Mon, 2 Dec 2024 22:36:52 +0800 Subject: [PATCH 3/6] chore: Update `copy` config type in Mako bundler - Updated the type of the `copy` property in the `BuildParams` interface to support both `string` and `{ from: string; to: string }`. - Ensured the `copy` configuration is properly validated to handle both types. --- crates/binding/src/lib.rs | 2 +- packages/bundler-mako/index.js | 6 +++--- packages/mako/binding.d.ts | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/crates/binding/src/lib.rs b/crates/binding/src/lib.rs index 703c19978..25719b1ff 100644 --- a/crates/binding/src/lib.rs +++ b/crates/binding/src/lib.rs @@ -74,7 +74,7 @@ pub struct BuildParams { }; } >; - copy?: string[]; + copy?: (string | { from: string; to: string })[]; codeSplitting?: | false | { diff --git a/packages/bundler-mako/index.js b/packages/bundler-mako/index.js index 04974f9b7..93a574467 100644 --- a/packages/bundler-mako/index.js +++ b/packages/bundler-mako/index.js @@ -249,13 +249,13 @@ function checkConfig(opts) { `umi config mako.${key} is not supported`, ); }); - // 暂不支持 { from, to } 格式 const { copy } = opts.config; if (copy) { for (const item of copy) { assert( - typeof item === 'string', - `copy config item must be string in Mako bundler, but got ${item}`, + typeof item === 'string' || + (typeof item === 'object' && item.from && item.to), + `copy config item must be string or { from: string, to: string } in Mako bundler, but got ${JSON.stringify(item)}`, ); } } diff --git a/packages/mako/binding.d.ts b/packages/mako/binding.d.ts index fa889880d..3dd8ab012 100644 --- a/packages/mako/binding.d.ts +++ b/packages/mako/binding.d.ts @@ -134,7 +134,7 @@ export interface BuildParams { }; } >; - copy?: string[]; + copy?: (string | { from: string; to: string })[]; codeSplitting?: | false | { From 5d42e4f20df42f4fcc7930d7abf626999d35f842 Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Mon, 2 Dec 2024 22:41:01 +0800 Subject: [PATCH 4/6] docs: Update `copy` config type in documentation - Updated the `copy` property type in the configuration documentation to reflect the change from `string[]` to `(string | { from: string; to: string })[]`. - Clarified that the `copy` configuration can now accept both strings and objects with `from` and `to` properties. --- docs/config.md | 2 +- docs/config.zh-CN.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/config.md b/docs/config.md index 2a5897b1a..fb9dbe83b 100644 --- a/docs/config.md +++ b/docs/config.md @@ -116,7 +116,7 @@ Specify the code splitting strategy. Use `auto` or `granular` strategy for SPA, ### copy -- Type: `string[]` +- Type: `(string | { from: string; to: string })[]` - Default: `["public"]` Specify the files or directories to be copied. By default, the files under the `public` directory will be copied to the output directory. diff --git a/docs/config.zh-CN.md b/docs/config.zh-CN.md index 2558196f2..dc796498f 100644 --- a/docs/config.zh-CN.md +++ b/docs/config.zh-CN.md @@ -116,7 +116,7 @@ ### copy -- 类型:`string[]` +- 类型:`(string | { from: string; to: string })[]` - 默认值:`["public"]` 指定需要复制的文件或目录。默认情况下,会将 `public` 目录下的文件复制到输出目录。 From f2c75e35ed32e40de551e318464395a2679ef0c2 Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Tue, 3 Dec 2024 01:51:20 +0800 Subject: [PATCH 5/6] test(copy): add e2e tests for copy plugin from/to pattern - Update config.copy test fixtures to cover from/to pattern - Add assertions for copied files in new location - Adjust copy plugin path validation --- crates/mako/src/plugins/copy.rs | 5 ++--- e2e/fixtures/config.copy/expect.js | 6 +++++- e2e/fixtures/config.copy/mako.config.json | 10 ++++++++-- 3 files changed, 15 insertions(+), 6 deletions(-) diff --git a/crates/mako/src/plugins/copy.rs b/crates/mako/src/plugins/copy.rs index 4c90312ce..1120126a3 100644 --- a/crates/mako/src/plugins/copy.rs +++ b/crates/mako/src/plugins/copy.rs @@ -80,9 +80,8 @@ impl CopyPlugin { let src = context.root.join(from); let target = dest.join(to.trim_start_matches("/")); - let target = target.canonicalize()?; - let dest_canonical = dest.canonicalize()?; - if !target.starts_with(&dest_canonical) { + let dest_path = dest.to_path_buf(); + if !target.starts_with(&dest_path) { return Err(anyhow!("Invalid target path: {:?}", target)); } diff --git a/e2e/fixtures/config.copy/expect.js b/e2e/fixtures/config.copy/expect.js index 7e305d64c..abac99d15 100644 --- a/e2e/fixtures/config.copy/expect.js +++ b/e2e/fixtures/config.copy/expect.js @@ -2,4 +2,8 @@ const assert = require("assert"); const { parseBuildResult } = require("../../../scripts/test-utils"); const { files } = parseBuildResult(__dirname); -assert("foo.js" in files, "assets files not copy"); +// Test original string pattern (copies to root) +assert("foo.js" in files, "assets files not copied (string pattern)"); + +// Test new from/to pattern (copies to assets-from-to directory) +assert("assets-from-to/foo.js" in files, "assets files not copied to correct location (from/to pattern)"); diff --git a/e2e/fixtures/config.copy/mako.config.json b/e2e/fixtures/config.copy/mako.config.json index 0e292c656..2b4d09b8d 100644 --- a/e2e/fixtures/config.copy/mako.config.json +++ b/e2e/fixtures/config.copy/mako.config.json @@ -1,3 +1,9 @@ { - "copy": ["src/assets"] -} + "copy": [ + "src/assets", + { + "from": "src/assets", + "to": "assets-from-to" + } + ] +} \ No newline at end of file From 459b3a05fba8680268c27ef43281fd2f59cb4df4 Mon Sep 17 00:00:00 2001 From: bqxbqx Date: Tue, 3 Dec 2024 11:06:56 +0800 Subject: [PATCH 6/6] fix(copy): improve path validation and cleanup for copy plugin - Add directory cleanup when path validation fails - Use canonicalized paths for more reliable path validation - Add concatenateModules option type to BuildParams --- crates/mako/src/plugins/copy.rs | 17 +++++++++++------ packages/mako/binding.d.ts | 1 + 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/crates/mako/src/plugins/copy.rs b/crates/mako/src/plugins/copy.rs index 1120126a3..37c5f5f6e 100644 --- a/crates/mako/src/plugins/copy.rs +++ b/crates/mako/src/plugins/copy.rs @@ -80,15 +80,20 @@ impl CopyPlugin { let src = context.root.join(from); let target = dest.join(to.trim_start_matches("/")); - let dest_path = dest.to_path_buf(); - if !target.starts_with(&dest_path) { + let was_created = if !target.exists() { + fs::create_dir_all(&target).is_ok() + } else { + false + }; + let canonical_target = target.canonicalize()?; + let canonical_dest_path = dest.canonicalize()?; + if !canonical_target.starts_with(&canonical_dest_path) { + if was_created { + fs::remove_dir_all(&target)?; + } return Err(anyhow!("Invalid target path: {:?}", target)); } - if !target.exists() { - fs::create_dir_all(&target)?; - } - debug!("copy {:?} to {:?}", src, target); copy(&src, &target)?; } diff --git a/packages/mako/binding.d.ts b/packages/mako/binding.d.ts index 3dd8ab012..0eeb93780 100644 --- a/packages/mako/binding.d.ts +++ b/packages/mako/binding.d.ts @@ -232,6 +232,7 @@ export interface BuildParams { | false | { skipModules?: boolean; + concatenateModules?: boolean; }; react?: { runtime?: 'automatic' | 'classic';