@@ -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,9 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
81124 if ( this . isBundleType ( ) ) {
82125 return path ;
83126 }
127+ if ( isWebAppBaseType ( path ) ) {
128+ return path ;
129+ }
84130 const pathToContent = dirname ( path ) ;
85131 const parts = pathToContent . split ( sep ) ;
86132 /* Handle mobile or tablet variants.Eg- digitalExperiences/site/lwr11/sfdc_cms__view/home/mobile/mobile.json
@@ -104,6 +150,9 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
104150 // for top level types we don't need to resolve parent
105151 return component ;
106152 }
153+ if ( isWebAppBaseType ( trigger ) ) {
154+ return this . populateWebAppBundle ( trigger , component ) ;
155+ }
107156 const source = super . populate ( trigger , component ) ;
108157 const parentType = this . registry . getParentType ( this . type . id ) ;
109158 // we expect source, parentType and content to be defined.
@@ -142,9 +191,45 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
142191 path : xml . path ,
143192 } ;
144193 }
194+ if ( xml && isWebAppBaseType ( path ) ) {
195+ return {
196+ fullName : this . getBundleName ( path ) ,
197+ suffix : xml . suffix ,
198+ path : xml . path ,
199+ } ;
200+ }
201+ }
202+
203+ private populateWebAppBundle ( trigger : string , component ?: SourceComponent ) : SourceComponent {
204+ if ( component ) {
205+ return component ;
206+ }
207+ const bundleName = this . getBundleName ( trigger ) ;
208+ const pathParts = trigger . split ( sep ) ;
209+ const bundleDir = pathParts . slice ( 0 , getDigitalExperiencesIndex ( trigger ) + 3 ) . join ( sep ) ;
210+ const parentType = this . isBundleType ( ) ? this . type : this . registry . getParentType ( this . type . id ) ;
211+ if ( ! parentType ) {
212+ throw messages . createError ( 'error_failed_convert' , [ bundleName ] ) ;
213+ }
214+ return new SourceComponent (
215+ {
216+ name : bundleName ,
217+ type : parentType ,
218+ content : bundleDir ,
219+ } ,
220+ this . tree ,
221+ this . forceIgnore
222+ ) ;
145223 }
146224
147225 private getBundleName ( contentPath : string ) : string {
226+ if ( isWebAppBaseType ( contentPath ) ) {
227+ const pathParts = contentPath . split ( sep ) ;
228+ const digitalExperiencesIndex = getDigitalExperiencesIndex ( contentPath ) ;
229+ const baseType = pathParts [ digitalExperiencesIndex + 1 ] ;
230+ const spaceApiName = pathParts [ digitalExperiencesIndex + 2 ] ;
231+ return `${ baseType } /${ spaceApiName } ` ;
232+ }
148233 const bundlePath = this . getBundleMetadataXmlPath ( contentPath ) ;
149234 return `${ parentName ( dirname ( bundlePath ) ) } /${ parentName ( bundlePath ) } ` ;
150235 }
@@ -154,6 +239,9 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
154239 // if this is the bundle type and it ends with -meta.xml, then this is the bundle metadata xml path
155240 return path ;
156241 }
242+ if ( isWebAppBaseType ( path ) ) {
243+ return '' ;
244+ }
157245 const pathParts = path . split ( sep ) ;
158246 const typeFolderIndex = pathParts . lastIndexOf ( this . type . directoryName ) ;
159247 // 3 because we want 'digitalExperiences' directory, 'baseType' directory and 'bundleName' directory
@@ -177,3 +265,28 @@ export class DigitalExperienceSourceAdapter extends BundleSourceAdapter {
177265const calculateNameFromPath = ( contentPath : string ) : string => `${ parentName ( contentPath ) } /${ baseName ( contentPath ) } ` ;
178266const digitalExperienceStructure = join ( 'BaseType' , 'SpaceApiName' , 'ContentType' , 'ContentApiName' ) ;
179267const contentParts = digitalExperienceStructure . split ( sep ) ;
268+
269+ /**
270+ * Checks if the given path belongs to the web_app base type.
271+ * web_app base type has a simpler structure without ContentType folders.
272+ * Structure: digitalExperiences/web_app/spaceApiName/...files...
273+ */
274+ const isWebAppBaseType = ( path : string ) : boolean => {
275+ const pathParts = path . split ( sep ) ;
276+ const digitalExperiencesIndex = pathParts . indexOf ( 'digitalExperiences' ) ;
277+ // Check if the base type (folder after digitalExperiences) is WEB_APP_BASE_TYPE
278+ return (
279+ digitalExperiencesIndex > - 1 &&
280+ pathParts . length > digitalExperiencesIndex + 1 &&
281+ pathParts [ digitalExperiencesIndex + 1 ] === WEB_APP_BASE_TYPE
282+ ) ;
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+ } ;
0 commit comments