8
8
import { sep , normalize , isAbsolute , relative } from 'node:path' ;
9
9
import * as fs from 'node:fs' ;
10
10
import { isString } from '@salesforce/ts-types' ;
11
- import { SourceComponent } from '@salesforce/source-deploy-retrieve' ;
11
+ import {
12
+ FileResponseSuccess ,
13
+ ForceIgnore ,
14
+ MetadataComponent ,
15
+ MetadataMember ,
16
+ RegistryAccess ,
17
+ SourceComponent ,
18
+ } from '@salesforce/source-deploy-retrieve' ;
12
19
import { XMLBuilder , XMLParser } from 'fast-xml-parser' ;
13
20
import { ensureArray } from '@salesforce/kit' ;
14
- import { RemoteChangeElement , ChangeResult } from './types' ;
21
+ import { RemoteChangeElement , ChangeResult , ChangeResultWithNameAndType , RemoteSyncInput } from './types' ;
22
+ import { ensureNameAndType } from './remoteChangeIgnoring' ;
15
23
16
24
export const getMetadataKey = ( metadataType : string , metadataName : string ) : string =>
17
25
`${ metadataType } __${ metadataName } ` ;
@@ -25,29 +33,39 @@ export const getKeyFromObject = (element: RemoteChangeElement | ChangeResult): s
25
33
26
34
export const supportsPartialDelete = ( cmp : SourceComponent ) : boolean => ! ! cmp . type . supportsPartialDelete ;
27
35
28
- export const isLwcLocalOnlyTest = ( filePath : string ) : boolean =>
29
- filePath . includes ( '__utam__' ) || filePath . includes ( '__tests__' ) ;
36
+ export const excludeLwcLocalOnlyTest = ( filePath : string ) : boolean =>
37
+ ! ( filePath . includes ( '__utam__' ) || filePath . includes ( '__tests__' ) ) ;
30
38
31
39
/**
32
40
* Verify that a filepath starts exactly with a complete parent path
33
41
* ex: '/foo/bar-extra/baz'.startsWith('foo/bar') would be true, but this function understands that they are not in the same folder
34
42
*/
35
- export const pathIsInFolder = ( filePath : string , folder : string ) : boolean => {
36
- const biggerStringParts = normalize ( filePath ) . split ( sep ) . filter ( nonEmptyStringFilter ) ;
37
- return normalize ( folder )
38
- . split ( sep )
39
- . filter ( nonEmptyStringFilter )
40
- . every ( ( part , index ) => part === biggerStringParts [ index ] ) ;
41
- } ;
43
+ export const pathIsInFolder =
44
+ ( folder : string ) =>
45
+ ( filePath : string ) : boolean => {
46
+ const biggerStringParts = normalize ( filePath ) . split ( sep ) . filter ( nonEmptyStringFilter ) ;
47
+ return normalize ( folder )
48
+ . split ( sep )
49
+ . filter ( nonEmptyStringFilter )
50
+ . every ( ( part , index ) => part === biggerStringParts [ index ] ) ;
51
+ } ;
52
+
53
+ /** just like pathIsInFolder but with the parameter order reversed for iterating a single file against an array of folders */
54
+ export const folderContainsPath =
55
+ ( filePath : string ) =>
56
+ ( folder : string ) : boolean =>
57
+ pathIsInFolder ( folder ) ( filePath ) ;
42
58
43
59
const nonEmptyStringFilter = ( value : string ) : boolean => isString ( value ) && value . length > 0 ;
44
60
45
61
// adapted for TS from https://github.com/30-seconds/30-seconds-of-code/blob/master/snippets/chunk.md
46
62
export const chunkArray = < T > ( arr : T [ ] , size : number ) : T [ ] [ ] =>
47
63
Array . from ( { length : Math . ceil ( arr . length / size ) } , ( v , i ) => arr . slice ( i * size , i * size + size ) ) ;
48
64
49
- export const ensureRelative = ( filePath : string , projectPath : string ) : string =>
50
- isAbsolute ( filePath ) ? relative ( projectPath , filePath ) : filePath ;
65
+ export const ensureRelative =
66
+ ( projectPath : string ) =>
67
+ ( filePath : string ) : string =>
68
+ isAbsolute ( filePath ) ? relative ( projectPath , filePath ) : filePath ;
51
69
52
70
export type ParsedCustomLabels = {
53
71
CustomLabels : { labels : Array < { fullName : string } > } ;
@@ -64,12 +82,12 @@ export const deleteCustomLabels = async (
64
82
filename : string ,
65
83
customLabels : SourceComponent [ ]
66
84
) : Promise < ParsedCustomLabels | undefined > => {
67
- const customLabelsToDelete = customLabels
68
- . filter ( ( label ) => label . type . id === 'customlabel' )
69
- . map ( ( change ) => change . fullName ) ;
85
+ const customLabelsToDelete = new Set (
86
+ customLabels . filter ( sourceComponentIsCustomLabel ) . map ( ( change ) => change . fullName )
87
+ ) ;
70
88
71
89
// if we don't have custom labels, we don't need to do anything
72
- if ( ! customLabelsToDelete . length ) {
90
+ if ( ! customLabelsToDelete . size ) {
73
91
return undefined ;
74
92
}
75
93
// for custom labels, we need to remove the individual label from the xml file
@@ -83,7 +101,7 @@ export const deleteCustomLabels = async (
83
101
84
102
// delete the labels from the json based on their fullName's
85
103
cls . CustomLabels . labels = ensureArray ( cls . CustomLabels . labels ) . filter (
86
- ( label ) => ! customLabelsToDelete . includes ( label . fullName )
104
+ ( label ) => ! customLabelsToDelete . has ( label . fullName )
87
105
) ;
88
106
89
107
if ( cls . CustomLabels . labels . length === 0 ) {
@@ -104,3 +122,35 @@ export const deleteCustomLabels = async (
104
122
return cls ;
105
123
}
106
124
} ;
125
+
126
+ /** returns true if forceIgnore denies a path OR if there is no forceIgnore provided */
127
+ export const forceIgnoreDenies =
128
+ ( forceIgnore ?: ForceIgnore ) =>
129
+ ( filePath : string ) : boolean =>
130
+ forceIgnore ?. denies ( filePath ) ?? false ;
131
+
132
+ export const sourceComponentIsCustomLabel = ( input : SourceComponent ) : boolean => input . type . name === 'CustomLabel' ;
133
+
134
+ export const sourceComponentHasFullNameAndType = ( input : SourceComponent ) : boolean =>
135
+ typeof input . fullName === 'string' && typeof input . type . name === 'string' ;
136
+
137
+ export const getAllFiles = ( sc : SourceComponent ) : string [ ] => [ sc . xml , ...sc . walkContent ( ) ] . filter ( isString ) ;
138
+
139
+ export const remoteChangeToMetadataMember = ( cr : ChangeResult ) : MetadataMember => {
140
+ const checked = ensureNameAndType ( cr ) ;
141
+
142
+ return {
143
+ fullName : checked . name ,
144
+ type : checked . type ,
145
+ } ;
146
+ } ;
147
+
148
+ // weird, right? This is for oclif.table which allows types but not interfaces. In this case, they are equivalent
149
+ export const FileResponseSuccessToRemoteSyncInput = ( fr : FileResponseSuccess ) : RemoteSyncInput => fr ;
150
+
151
+ export const changeResultToMetadataComponent =
152
+ ( registry : RegistryAccess = new RegistryAccess ( ) ) =>
153
+ ( cr : ChangeResultWithNameAndType ) : MetadataComponent => ( {
154
+ fullName : cr . name ,
155
+ type : registry . getTypeByName ( cr . type ) ,
156
+ } ) ;
0 commit comments