@@ -400,6 +400,66 @@ fn escape_single_quotes(s: &str) -> String {
400400 s. replace ( '\\' , "\\ \\ " ) . replace ( '\'' , "\\ '" )
401401}
402402
403+ /// Merge tsdown config into vite.config.ts by importing it
404+ ///
405+ /// This function adds an import statement for the tsdown config file
406+ /// and adds `lib: libConfig` to the defineConfig.
407+ ///
408+ /// # Arguments
409+ ///
410+ /// * `vite_config_path` - Path to the vite.config.ts or vite.config.js file
411+ /// * `tsdown_config_path` - Path to the tsdown.config.ts file (relative path like "./tsdown.config.ts")
412+ ///
413+ /// # Returns
414+ ///
415+ /// Returns a `MergeResult` with the updated content
416+ pub fn merge_tsdown_config (
417+ vite_config_path : & Path ,
418+ tsdown_config_path : & str ,
419+ ) -> Result < MergeResult , Error > {
420+ let vite_config_content = std:: fs:: read_to_string ( vite_config_path) ?;
421+ merge_tsdown_config_content ( & vite_config_content, tsdown_config_path)
422+ }
423+
424+ /// Merge tsdown config into vite config content
425+ ///
426+ /// This adds:
427+ /// 1. An import statement: `import libConfig from './tsdown.config.ts'`
428+ /// 2. The lib config in defineConfig: `lib: libConfig`
429+ ///
430+ /// This function is idempotent - running it multiple times will not create duplicates.
431+ fn merge_tsdown_config_content (
432+ vite_config_content : & str ,
433+ tsdown_config_path : & str ,
434+ ) -> Result < MergeResult , Error > {
435+ let uses_function_callback = check_function_callback ( vite_config_content) ?;
436+
437+ // Check if already migrated (idempotency check)
438+ if vite_config_content. contains ( "import libConfig from" ) {
439+ return Ok ( MergeResult {
440+ content : vite_config_content. to_string ( ) ,
441+ updated : false ,
442+ uses_function_callback,
443+ } ) ;
444+ }
445+
446+ // Step 1: Add import statement at the beginning
447+ // Use .js extension for TypeScript files (TypeScript convention)
448+ let import_path = if tsdown_config_path. ends_with ( ".ts" ) {
449+ tsdown_config_path. replace ( ".ts" , ".js" )
450+ } else {
451+ tsdown_config_path. to_string ( )
452+ } ;
453+ let content_with_import =
454+ format ! ( "import libConfig from '{import_path}';\n \n {vite_config_content}" ) ;
455+
456+ // Step 2: Add lib: libConfig to defineConfig
457+ let lib_rule = generate_merge_rule ( "libConfig" , "lib" ) ;
458+ let ( final_content, _) = ast_grep:: apply_rules ( & content_with_import, & lib_rule) ?;
459+
460+ Ok ( MergeResult { content : final_content, updated : true , uses_function_callback } )
461+ }
462+
403463#[ cfg( test) ]
404464mod tests {
405465 use std:: io:: Write ;
@@ -1044,4 +1104,159 @@ export default defineConfig({
10441104})"
10451105 ) ;
10461106 }
1107+
1108+ #[ test]
1109+ fn test_merge_tsdown_config_content_simple ( ) {
1110+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1111+
1112+ export default defineConfig({
1113+ plugins: [],
1114+ });"# ;
1115+
1116+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1117+ assert ! ( result. updated) ;
1118+ assert ! ( !result. uses_function_callback) ;
1119+ // TypeScript files use .js extension in imports
1120+ assert_eq ! (
1121+ result. content,
1122+ r#"import libConfig from './tsdown.config.js';
1123+
1124+ import { defineConfig } from '@voidzero-dev/vite-plus';
1125+
1126+ export default defineConfig({
1127+ lib: libConfig,
1128+ plugins: [],
1129+ });"#
1130+ ) ;
1131+ }
1132+
1133+ #[ test]
1134+ fn test_merge_tsdown_config_content_with_existing_imports ( ) {
1135+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1136+ import react from '@vitejs/plugin-react';
1137+
1138+ export default defineConfig({
1139+ plugins: [react()],
1140+ });"# ;
1141+
1142+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1143+ assert ! ( result. updated) ;
1144+ assert ! ( !result. uses_function_callback) ;
1145+ assert_eq ! (
1146+ result. content,
1147+ r#"import libConfig from './tsdown.config.js';
1148+
1149+ import { defineConfig } from '@voidzero-dev/vite-plus';
1150+ import react from '@vitejs/plugin-react';
1151+
1152+ export default defineConfig({
1153+ lib: libConfig,
1154+ plugins: [react()],
1155+ });"#
1156+ ) ;
1157+ }
1158+
1159+ #[ test]
1160+ fn test_merge_tsdown_config_content_function_callback ( ) {
1161+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1162+
1163+ export default defineConfig((env) => ({
1164+ plugins: [],
1165+ }));"# ;
1166+
1167+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1168+ assert ! ( result. updated) ;
1169+ assert ! ( result. uses_function_callback) ;
1170+ assert_eq ! (
1171+ result. content,
1172+ r#"import libConfig from './tsdown.config.js';
1173+
1174+ import { defineConfig } from '@voidzero-dev/vite-plus';
1175+
1176+ export default defineConfig((env) => ({
1177+ lib: libConfig,
1178+ plugins: [],
1179+ }));"#
1180+ ) ;
1181+ }
1182+
1183+ #[ test]
1184+ fn test_merge_tsdown_config_content_idempotent ( ) {
1185+ // Already migrated config - import at the beginning
1186+ let already_migrated = r#"import libConfig from './tsdown.config.js';
1187+
1188+ import { defineConfig } from '@voidzero-dev/vite-plus';
1189+
1190+ export default defineConfig({
1191+ lib: libConfig,
1192+ plugins: [],
1193+ });"# ;
1194+
1195+ let result = merge_tsdown_config_content ( already_migrated, "./tsdown.config.ts" ) . unwrap ( ) ;
1196+ assert ! ( !result. updated, "Should not update already migrated config" ) ;
1197+ assert_eq ! ( result. content, already_migrated) ;
1198+
1199+ // Run migration twice and verify no duplicates
1200+ let fresh_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1201+
1202+ export default defineConfig({
1203+ plugins: [],
1204+ });"# ;
1205+
1206+ let expected_migrated = r#"import libConfig from './tsdown.config.js';
1207+
1208+ import { defineConfig } from '@voidzero-dev/vite-plus';
1209+
1210+ export default defineConfig({
1211+ lib: libConfig,
1212+ plugins: [],
1213+ });"# ;
1214+
1215+ let first_result = merge_tsdown_config_content ( fresh_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1216+ assert ! ( first_result. updated) ;
1217+ assert_eq ! ( first_result. content, expected_migrated) ;
1218+
1219+ // Run again on the result - should return unchanged
1220+ let second_result =
1221+ merge_tsdown_config_content ( & first_result. content , "./tsdown.config.ts" ) . unwrap ( ) ;
1222+ assert ! ( !second_result. updated, "Second migration should not update" ) ;
1223+ assert_eq ! ( second_result. content, expected_migrated) ;
1224+ }
1225+
1226+ #[ test]
1227+ fn test_merge_tsdown_config_content_no_imports ( ) {
1228+ // vite.config.ts without any import statements
1229+ let vite_config = r#"export default {
1230+ server: { port: 3000 }
1231+ }"# ;
1232+
1233+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1234+ assert ! ( result. updated) ;
1235+ assert ! ( !result. uses_function_callback) ;
1236+ assert_eq ! (
1237+ result. content,
1238+ r#"import libConfig from './tsdown.config.js';
1239+
1240+ export default {
1241+ lib: libConfig,
1242+ server: { port: 3000 }
1243+ }"#
1244+ ) ;
1245+ }
1246+
1247+ #[ test]
1248+ fn test_merge_tsdown_config_content_no_false_positive_stdlib ( ) {
1249+ // "stdlib:" should not be detected as "lib:" key
1250+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1251+
1252+ export default defineConfig({
1253+ stdlib: 'some-value',
1254+ });"# ;
1255+
1256+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1257+ assert ! ( result. updated) ;
1258+ assert ! ( result. content. contains( "import libConfig from './tsdown.config.js'" ) ) ;
1259+ assert ! ( result. content. contains( "lib: libConfig" ) ) ;
1260+ assert ! ( result. content. contains( "stdlib: 'some-value'" ) ) ;
1261+ }
10471262}
0 commit comments