@@ -25,6 +25,10 @@ import { BundleSourceAdapter } from './bundleSourceAdapter';
2525
2626Messages . importMessagesDirectory ( __dirname ) ;
2727const messages = Messages . loadMessages ( '@salesforce/source-deploy-retrieve' , 'sdr' ) ;
28+
29+ // Constants for DigitalExperience base types
30+ const WEB_APP_BASE_TYPE = 'web_app' ;
31+
2832/**
2933 * Source Adapter for DigitalExperience metadata types. This metadata type is a bundled type of the format
3034 *
@@ -58,18 +62,57 @@ const messages = Messages.loadMessages('@salesforce/source-deploy-retrieve', 'sd
5862 * content/
5963 * ├── bars/
6064 * | ├── bars.digitalExperience-meta.xml
65+ * web_app/
66+ * ├── zenith/
67+ * | ├── css/
68+ * | | ├── header/
69+ * | | | ├── header.css
70+ * | | ├── home.css
71+ * | ├── js/
72+ * | | ├── home.js
73+ * | ├── html/
74+ * | | ├── home.html
75+ * | ├── images/
76+ * | | ├── logos/
77+ * | | | ├── logo.png
6178 * ```
6279 *
6380 * In the above structure the metadata xml file ending with "digitalExperience-meta.xml" belongs to DigitalExperienceBundle MD type.
6481 * The "_meta.json" files are child metadata files of DigitalExperienceBundle belonging to DigitalExperience MD type. The rest of the files in the
6582 * corresponding folder are the contents to the DigitalExperience metadata. So, incase of DigitalExperience the metadata file is a JSON file
66- * and not an XML file
83+ * and not an XML file.
84+ *
85+ * For web_app base type, the bundle is identified by directory structure alone without metadata XML files.
6786 */
6887export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
88+ public getComponent ( path : SourcePath , isResolvingSource = true ) : SourceComponent | undefined {
89+ if ( this . isBundleType ( ) && isWebAppBaseType ( path ) && this . tree . isDirectory ( path ) ) {
90+ const pathParts = path . split ( sep ) ;
91+ const bundleNameIndex = getDigitalExperiencesIndex ( path ) + 2 ;
92+ if ( bundleNameIndex === pathParts . length - 1 ) {
93+ return this . populate ( path , undefined ) ;
94+ }
95+ }
96+ return super . getComponent ( path , isResolvingSource ) ;
97+ }
98+
99+ protected parseAsRootMetadataXml ( path : string ) : MetadataXml | undefined {
100+ if ( isWebAppBaseType ( path ) ) {
101+ return undefined ;
102+ }
103+ if ( ! this . isBundleType ( ) && ! path . endsWith ( this . type . metaFileSuffix ?? '_meta.json' ) ) {
104+ return undefined ;
105+ }
106+ return super . parseAsRootMetadataXml ( path ) ;
107+ }
108+
69109 protected getRootMetadataXmlPath ( trigger : string ) : string {
70110 if ( this . isBundleType ( ) ) {
71111 return this . getBundleMetadataXmlPath ( trigger ) ;
72112 }
113+ if ( isWebAppBaseType ( trigger ) ) {
114+ return '' ;
115+ }
73116 // metafile name = metaFileSuffix for DigitalExperience.
74117 if ( ! this . type . metaFileSuffix ) {
75118 throw messages . createError ( 'missingMetaFileSuffix' , [ this . type . name ] ) ;
@@ -81,6 +124,10 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
81124 if ( this . isBundleType ( ) ) {
82125 return path ;
83126 }
127+ if ( isWebAppBaseType ( path ) ) {
128+ // For web_app, trim to the bundle directory: digitalExperiences/web_app/WebApp
129+ return getWebAppBundleDir ( path ) ;
130+ }
84131 const pathToContent = dirname ( path ) ;
85132 const parts = pathToContent . split ( sep ) ;
86133 /* Handle mobile or tablet variants.Eg- digitalExperiences/site/lwr11/sfdc_cms__view/home/mobile/mobile.json
@@ -104,6 +151,9 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
104151 // for top level types we don't need to resolve parent
105152 return component ;
106153 }
154+ if ( isWebAppBaseType ( trigger ) ) {
155+ return this . populateWebAppBundle ( trigger , component ) ;
156+ }
107157 const source = super . populate ( trigger , component ) ;
108158 const parentType = this . registry . getParentType ( this . type . id ) ;
109159 // we expect source, parentType and content to be defined.
@@ -144,16 +194,59 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
144194 }
145195 }
146196
197+ private populateWebAppBundle ( trigger : string , component ?: SourceComponent ) : SourceComponent {
198+ if ( component ) {
199+ return component ;
200+ }
201+
202+ const pathParts = trigger . split ( sep ) ;
203+ const digitalExperiencesIndex = pathParts . indexOf ( 'digitalExperiences' ) ;
204+
205+ // Extract bundle name: web_app/WebApp3 (always use posix separator for metadata names)
206+ const baseType = pathParts [ digitalExperiencesIndex + 1 ] ;
207+ const spaceApiName = pathParts [ digitalExperiencesIndex + 2 ] ;
208+ const bundleName = [ baseType , spaceApiName ] . join ( '/' ) ;
209+
210+ // Extract bundle directory: /path/to/digitalExperiences/web_app/WebApp3
211+ const bundleDir = getWebAppBundleDir ( trigger ) ;
212+
213+ // Get the DigitalExperienceBundle type
214+ const parentType = this . isBundleType ( ) ? this . type : this . registry . getParentType ( this . type . id ) ;
215+ if ( ! parentType ) {
216+ throw messages . createError ( 'error_failed_convert' , [ bundleName ] ) ;
217+ }
218+
219+ return new SourceComponent (
220+ {
221+ name : bundleName ,
222+ type : parentType ,
223+ content : bundleDir ,
224+ } ,
225+ this . tree ,
226+ this . forceIgnore
227+ ) ;
228+ }
229+
147230 private getBundleName ( contentPath : string ) : string {
231+ if ( isWebAppBaseType ( contentPath ) ) {
232+ const pathParts = contentPath . split ( sep ) ;
233+ const digitalExperiencesIndex = getDigitalExperiencesIndex ( contentPath ) ;
234+ const baseType = pathParts [ digitalExperiencesIndex + 1 ] ;
235+ const spaceApiName = pathParts [ digitalExperiencesIndex + 2 ] ;
236+ return [ baseType , spaceApiName ] . join ( '/' ) ;
237+ }
148238 const bundlePath = this . getBundleMetadataXmlPath ( contentPath ) ;
149- return ` ${ parentName ( dirname ( bundlePath ) ) } / ${ parentName ( bundlePath ) } ` ;
239+ return [ parentName ( dirname ( bundlePath ) ) , parentName ( bundlePath ) ] . join ( '/' ) ;
150240 }
151241
152242 private getBundleMetadataXmlPath ( path : string ) : string {
153243 if ( this . isBundleType ( ) && path . endsWith ( META_XML_SUFFIX ) ) {
154244 // if this is the bundle type and it ends with -meta.xml, then this is the bundle metadata xml path
155245 return path ;
156246 }
247+ if ( isWebAppBaseType ( path ) ) {
248+ return '' ;
249+ }
157250 const pathParts = path . split ( sep ) ;
158251 const typeFolderIndex = pathParts . lastIndexOf ( this . type . directoryName ) ;
159252 // 3 because we want 'digitalExperiences' directory, 'baseType' directory and 'bundleName' directory
@@ -177,3 +270,38 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
177270const calculateNameFromPath = ( contentPath : string ) : string => `${ parentName ( contentPath ) } /${ baseName ( contentPath ) } ` ;
178271const digitalExperienceStructure = join ( 'BaseType' , 'SpaceApiName' , 'ContentType' , 'ContentApiName' ) ;
179272const contentParts = digitalExperienceStructure . split ( sep ) ;
273+
274+ /**
275+ * Checks if the given path belongs to the web_app base type.
276+ * web_app base type has a simpler structure without ContentType folders.
277+ * Structure: digitalExperiences/web_app/spaceApiName/...files...
278+ */
279+ export const isWebAppBaseType = ( path : string ) : boolean => {
280+ const pathParts = path . split ( sep ) ;
281+ const digitalExperiencesIndex = pathParts . indexOf ( 'digitalExperiences' ) ;
282+ return pathParts [ digitalExperiencesIndex + 1 ] === WEB_APP_BASE_TYPE ;
283+ } ;
284+
285+ /**
286+ * Gets the digitalExperiences index from a path.
287+ * Returns -1 if not found.
288+ */
289+ const getDigitalExperiencesIndex = ( path : string ) : number => {
290+ const pathParts = path . split ( sep ) ;
291+ return pathParts . indexOf ( 'digitalExperiences' ) ;
292+ } ;
293+
294+ /**
295+ * Gets the web_app bundle directory path.
296+ * For a path like: /path/to/digitalExperiences/web_app/WebApp/src/App.js
297+ * Returns: /path/to/digitalExperiences/web_app/WebApp
298+ */
299+ const getWebAppBundleDir = ( path : string ) : string => {
300+ const pathParts = path . split ( sep ) ;
301+ const digitalExperiencesIndex = pathParts . indexOf ( 'digitalExperiences' ) ;
302+ if ( digitalExperiencesIndex > - 1 && pathParts . length > digitalExperiencesIndex + 3 ) {
303+ // Return up to digitalExperiences/web_app/spaceApiName
304+ return pathParts . slice ( 0 , digitalExperiencesIndex + 3 ) . join ( sep ) ;
305+ }
306+ return path ;
307+ } ;
0 commit comments