@@ -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 ;
@@ -1033,4 +1093,159 @@ export default defineConfig({
10331093})"
10341094 ) ;
10351095 }
1096+
1097+ #[ test]
1098+ fn test_merge_tsdown_config_content_simple ( ) {
1099+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1100+
1101+ export default defineConfig({
1102+ plugins: [],
1103+ });"# ;
1104+
1105+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1106+ assert ! ( result. updated) ;
1107+ assert ! ( !result. uses_function_callback) ;
1108+ // TypeScript files use .js extension in imports
1109+ assert_eq ! (
1110+ result. content,
1111+ r#"import libConfig from './tsdown.config.js';
1112+
1113+ import { defineConfig } from '@voidzero-dev/vite-plus';
1114+
1115+ export default defineConfig({
1116+ lib: libConfig,
1117+ plugins: [],
1118+ });"#
1119+ ) ;
1120+ }
1121+
1122+ #[ test]
1123+ fn test_merge_tsdown_config_content_with_existing_imports ( ) {
1124+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1125+ import react from '@vitejs/plugin-react';
1126+
1127+ export default defineConfig({
1128+ plugins: [react()],
1129+ });"# ;
1130+
1131+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1132+ assert ! ( result. updated) ;
1133+ assert ! ( !result. uses_function_callback) ;
1134+ assert_eq ! (
1135+ result. content,
1136+ r#"import libConfig from './tsdown.config.js';
1137+
1138+ import { defineConfig } from '@voidzero-dev/vite-plus';
1139+ import react from '@vitejs/plugin-react';
1140+
1141+ export default defineConfig({
1142+ lib: libConfig,
1143+ plugins: [react()],
1144+ });"#
1145+ ) ;
1146+ }
1147+
1148+ #[ test]
1149+ fn test_merge_tsdown_config_content_function_callback ( ) {
1150+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1151+
1152+ export default defineConfig((env) => ({
1153+ plugins: [],
1154+ }));"# ;
1155+
1156+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1157+ assert ! ( result. updated) ;
1158+ assert ! ( result. uses_function_callback) ;
1159+ assert_eq ! (
1160+ result. content,
1161+ r#"import libConfig from './tsdown.config.js';
1162+
1163+ import { defineConfig } from '@voidzero-dev/vite-plus';
1164+
1165+ export default defineConfig((env) => ({
1166+ lib: libConfig,
1167+ plugins: [],
1168+ }));"#
1169+ ) ;
1170+ }
1171+
1172+ #[ test]
1173+ fn test_merge_tsdown_config_content_idempotent ( ) {
1174+ // Already migrated config - import at the beginning
1175+ let already_migrated = r#"import libConfig from './tsdown.config.js';
1176+
1177+ import { defineConfig } from '@voidzero-dev/vite-plus';
1178+
1179+ export default defineConfig({
1180+ lib: libConfig,
1181+ plugins: [],
1182+ });"# ;
1183+
1184+ let result = merge_tsdown_config_content ( already_migrated, "./tsdown.config.ts" ) . unwrap ( ) ;
1185+ assert ! ( !result. updated, "Should not update already migrated config" ) ;
1186+ assert_eq ! ( result. content, already_migrated) ;
1187+
1188+ // Run migration twice and verify no duplicates
1189+ let fresh_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1190+
1191+ export default defineConfig({
1192+ plugins: [],
1193+ });"# ;
1194+
1195+ let expected_migrated = r#"import libConfig from './tsdown.config.js';
1196+
1197+ import { defineConfig } from '@voidzero-dev/vite-plus';
1198+
1199+ export default defineConfig({
1200+ lib: libConfig,
1201+ plugins: [],
1202+ });"# ;
1203+
1204+ let first_result = merge_tsdown_config_content ( fresh_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1205+ assert ! ( first_result. updated) ;
1206+ assert_eq ! ( first_result. content, expected_migrated) ;
1207+
1208+ // Run again on the result - should return unchanged
1209+ let second_result =
1210+ merge_tsdown_config_content ( & first_result. content , "./tsdown.config.ts" ) . unwrap ( ) ;
1211+ assert ! ( !second_result. updated, "Second migration should not update" ) ;
1212+ assert_eq ! ( second_result. content, expected_migrated) ;
1213+ }
1214+
1215+ #[ test]
1216+ fn test_merge_tsdown_config_content_no_imports ( ) {
1217+ // vite.config.ts without any import statements
1218+ let vite_config = r#"export default {
1219+ server: { port: 3000 }
1220+ }"# ;
1221+
1222+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1223+ assert ! ( result. updated) ;
1224+ assert ! ( !result. uses_function_callback) ;
1225+ assert_eq ! (
1226+ result. content,
1227+ r#"import libConfig from './tsdown.config.js';
1228+
1229+ export default {
1230+ lib: libConfig,
1231+ server: { port: 3000 }
1232+ }"#
1233+ ) ;
1234+ }
1235+
1236+ #[ test]
1237+ fn test_merge_tsdown_config_content_no_false_positive_stdlib ( ) {
1238+ // "stdlib:" should not be detected as "lib:" key
1239+ let vite_config = r#"import { defineConfig } from '@voidzero-dev/vite-plus';
1240+
1241+ export default defineConfig({
1242+ stdlib: 'some-value',
1243+ });"# ;
1244+
1245+ let result = merge_tsdown_config_content ( vite_config, "./tsdown.config.ts" ) . unwrap ( ) ;
1246+ assert ! ( result. updated) ;
1247+ assert ! ( result. content. contains( "import libConfig from './tsdown.config.js'" ) ) ;
1248+ assert ! ( result. content. contains( "lib: libConfig" ) ) ;
1249+ assert ! ( result. content. contains( "stdlib: 'some-value'" ) ) ;
1250+ }
10361251}
0 commit comments