@@ -3,6 +3,7 @@ import * as fs from 'fs';
3
3
import * as path from 'path' ;
4
4
import { exec } from 'child_process' ;
5
5
import { promisify } from 'util' ;
6
+ import { DOMParser } from 'xmldom' ;
6
7
7
8
const execAsync = promisify ( exec ) ;
8
9
@@ -63,6 +64,50 @@ async function revertPackageXmlIfChanged(workspacePath: string): Promise<void> {
63
64
}
64
65
}
65
66
67
+ // Añadir esta función helper
68
+ function mergePackageXmls ( xmlFiles : string [ ] ) : string {
69
+ const parser = new DOMParser ( ) ;
70
+ let mergedTypes : { [ key : string ] : Set < string > } = { } ;
71
+
72
+ xmlFiles . forEach ( xmlContent => {
73
+ const doc = parser . parseFromString ( xmlContent , 'text/xml' ) ;
74
+ const types = doc . getElementsByTagName ( 'types' ) ;
75
+
76
+ for ( const type of Array . from ( types ) ) {
77
+ const name = type . getElementsByTagName ( 'name' ) [ 0 ] . textContent ;
78
+ const members = Array . from ( type . getElementsByTagName ( 'members' ) )
79
+ . map ( member => member . textContent ) ;
80
+
81
+ if ( name ) {
82
+ if ( ! mergedTypes [ name ] ) {
83
+ mergedTypes [ name ] = new Set ( ) ;
84
+ }
85
+ members . forEach ( member => {
86
+ if ( member ) mergedTypes [ name ] . add ( member ) ;
87
+ } ) ;
88
+ }
89
+ }
90
+ } ) ;
91
+
92
+ // Crear el XML combinado
93
+ let result = '<?xml version="1.0" encoding="UTF-8" standalone="yes"?>\n' ;
94
+ result += '<Package xmlns="http://soap.sforce.com/2006/04/metadata">\n' ;
95
+
96
+ Object . entries ( mergedTypes ) . forEach ( ( [ name , members ] ) => {
97
+ result += '\t<types>\n' ;
98
+ Array . from ( members ) . sort ( ) . forEach ( member => {
99
+ result += `\t\t<members>${ member } </members>\n` ;
100
+ } ) ;
101
+ result += `\t\t<name>${ name } </name>\n` ;
102
+ result += '\t</types>\n' ;
103
+ } ) ;
104
+
105
+ result += '\t<version>60.0</version>\n' ;
106
+ result += '</Package>' ;
107
+
108
+ return result ;
109
+ }
110
+
66
111
export function activate ( context : vscode . ExtensionContext ) {
67
112
let configCommand = vscode . commands . registerCommand ( 'extension.configurePackageDuplicator' , async ( ) => {
68
113
const workspaceFolder = vscode . workspace . workspaceFolders ?. [ 0 ] ;
@@ -173,5 +218,121 @@ export function activate(context: vscode.ExtensionContext) {
173
218
}
174
219
} ) ;
175
220
176
- context . subscriptions . push ( disposable , configCommand ) ;
221
+ // Añadir el nuevo comando en activate()
222
+ let mergeCommand = vscode . commands . registerCommand ( 'extension.mergePackages' , async ( ) => {
223
+ const workspaceFolder = vscode . workspace . workspaceFolders ?. [ 0 ] ;
224
+ if ( ! workspaceFolder ) {
225
+ vscode . window . showErrorMessage ( 'No se encontró la carpeta del workspace' ) ;
226
+ return ;
227
+ }
228
+
229
+ const manifestFolder = path . join ( workspaceFolder . uri . fsPath , 'manifest' ) ;
230
+
231
+ // Preguntar el modo de selección
232
+ const selectionMode = await vscode . window . showQuickPick (
233
+ [
234
+ { label : 'Seleccionar archivos individuales' , value : 'files' } ,
235
+ { label : 'Seleccionar carpeta completa' , value : 'folder' }
236
+ ] ,
237
+ { placeHolder : '¿Cómo quieres seleccionar los archivos a combinar?' }
238
+ ) ;
239
+
240
+ if ( ! selectionMode ) return ;
241
+
242
+ try {
243
+ let xmlFiles : string [ ] = [ ] ;
244
+
245
+ if ( selectionMode . value === 'folder' ) {
246
+ // Obtener lista de carpetas
247
+ const folders = [ 'manifest' , ...fs . readdirSync ( manifestFolder )
248
+ . filter ( item => {
249
+ try {
250
+ return fs . statSync ( path . join ( manifestFolder , item ) ) . isDirectory ( ) ;
251
+ } catch ( error ) {
252
+ return false ;
253
+ }
254
+ } ) ] ;
255
+
256
+ const selectedFolder = await vscode . window . showQuickPick ( folders , {
257
+ placeHolder : 'Selecciona la carpeta que contiene los archivos a combinar'
258
+ } ) ;
259
+
260
+ if ( ! selectedFolder ) return ;
261
+
262
+ const folderPath = selectedFolder === 'manifest'
263
+ ? manifestFolder
264
+ : path . join ( manifestFolder , selectedFolder ) ;
265
+
266
+ // Leer todos los XML de la carpeta seleccionada
267
+ xmlFiles = fs . readdirSync ( folderPath )
268
+ . filter ( file => file . endsWith ( '.xml' ) )
269
+ . map ( file => path . join ( folderPath , file ) ) ;
270
+
271
+ if ( xmlFiles . length < 2 ) {
272
+ vscode . window . showInformationMessage ( 'Se necesitan al menos 2 archivos XML en la carpeta para combinar' ) ;
273
+ return ;
274
+ }
275
+ } else {
276
+ // Modo selección de archivos individual (código existente)
277
+ const processDirectory = ( dir : string ) => {
278
+ const items = fs . readdirSync ( dir ) ;
279
+ items . forEach ( item => {
280
+ const fullPath = path . join ( dir , item ) ;
281
+ if ( fs . statSync ( fullPath ) . isDirectory ( ) ) {
282
+ processDirectory ( fullPath ) ;
283
+ } else if ( item . endsWith ( '.xml' ) ) {
284
+ xmlFiles . push ( fullPath ) ;
285
+ }
286
+ } ) ;
287
+ } ;
288
+ processDirectory ( manifestFolder ) ;
289
+
290
+ const fileItems = xmlFiles . map ( file => ( {
291
+ label : path . relative ( manifestFolder , file ) ,
292
+ path : file
293
+ } ) ) ;
294
+
295
+ const selectedFiles = await vscode . window . showQuickPick ( fileItems , {
296
+ canPickMany : true ,
297
+ placeHolder : 'Selecciona los archivos package.xml a combinar'
298
+ } ) ;
299
+
300
+ if ( ! selectedFiles || selectedFiles . length < 2 ) {
301
+ vscode . window . showInformationMessage ( 'Debes seleccionar al menos 2 archivos para combinar' ) ;
302
+ return ;
303
+ }
304
+
305
+ xmlFiles = selectedFiles . map ( file => file . path ) ;
306
+ }
307
+
308
+ // Leer y combinar los archivos
309
+ const xmlContents = xmlFiles . map ( file => fs . readFileSync ( file , 'utf8' ) ) ;
310
+ const mergedContent = mergePackageXmls ( xmlContents ) ;
311
+
312
+ // Solicitar nombre del archivo
313
+ const defaultFileName = `merged-package-${ new Date ( ) . toISOString ( ) . replace ( / [: .] / g, '-' ) } .xml` ;
314
+ const fileName = await vscode . window . showInputBox ( {
315
+ prompt : 'Ingresa el nombre para el archivo combinado' ,
316
+ placeHolder : 'Ejemplo: merged-feature-123.xml' ,
317
+ value : defaultFileName
318
+ } ) ;
319
+
320
+ if ( ! fileName ) {
321
+ return ;
322
+ }
323
+
324
+ // Asegurar que el archivo termine en .xml
325
+ const finalFileName = fileName . endsWith ( '.xml' ) ? fileName : `${ fileName } .xml` ;
326
+ const newFilePath = path . join ( manifestFolder , finalFileName ) ;
327
+
328
+ fs . writeFileSync ( newFilePath , mergedContent ) ;
329
+ vscode . window . showInformationMessage ( `Archivos combinados en: ${ finalFileName } ` ) ;
330
+
331
+ } catch ( error ) {
332
+ const errorMessage = error instanceof Error ? error . message : 'Error desconocido' ;
333
+ vscode . window . showErrorMessage ( `Error al combinar archivos: ${ errorMessage } ` ) ;
334
+ }
335
+ } ) ;
336
+
337
+ context . subscriptions . push ( disposable , configCommand , mergeCommand ) ;
177
338
}
0 commit comments