-
Notifications
You must be signed in to change notification settings - Fork 165
Expand file tree
/
Copy pathpath.ts
More file actions
160 lines (147 loc) · 5.86 KB
/
path.ts
File metadata and controls
160 lines (147 loc) · 5.86 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
/*
* Copyright (c) 2020, salesforce.com, inc.
* All rights reserved.
* Licensed under the BSD 3-Clause license.
* For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
*/
import { basename, dirname, extname, join, sep } from 'node:path';
import { Optional } from '@salesforce/ts-types';
import { SfdxFileFormat } from '../convert/types';
import { SourcePath } from '../common/types';
import { DEFAULT_PACKAGE_ROOT_SFDX, META_XML_SUFFIX } from '../common/constants';
import { MetadataXml } from '../resolve/types';
import { MetadataType } from '../registry/types';
/**
* Get the file or directory name at the end of a path. Different from `path.basename`
* in that it strips anything after the first '.' in the name.
*
* @param fsPath The path to evaluate
*/
export function baseName(fsPath: SourcePath): string {
return basename(fsPath).split('.')[0];
}
/**
* the above baseName function doesn't handle components whose names have a `.` in them.
* this will handle that, but requires you to specify the mdType to check suffixes for.
*
* @param fsPath The path to evaluate
*/
export function baseWithoutSuffixes(fsPath: SourcePath, mdType: MetadataType): string {
return basename(fsPath).replace(META_XML_SUFFIX, '').split('.').filter(stringIsNotSuffix(mdType)).join('.');
}
const stringIsNotSuffix =
(type: MetadataType) =>
(part: string): boolean =>
part !== type.suffix && (!type.legacySuffix || part !== type.legacySuffix);
/**
* Get the name of file path extension. Different from path.extname in that it
* does not include the '.' in the extension name. Returns an empty string if
* there is no extension.
*
* @param fsPath The path to evaluate
*/
export function extName(fsPath: SourcePath): string {
const split = extname(fsPath).split('.');
return split.length > 1 ? split[1] : split[0];
}
/**
* Get the name of the parent to the last portion of a path
*
* @param fsPath The path to evaluate
*/
export function parentName(fsPath: SourcePath): string {
return basename(dirname(fsPath));
}
/**
* Trim a path up until and including the given part. Returns `fsPath`
* if the path `part` was not found.
*
* @param fsPath Path to trim
* @param part Path part to trim up until
* @param untilLast Trim until the *last* occurrence of `part`
*/
export function trimUntil(fsPath: SourcePath, part: string, untilLast = false): string {
const parts = fsPath.split(sep);
const partIndex = untilLast ? parts.lastIndexOf(part) : parts.findIndex((p) => part === p);
if (partIndex === -1) {
return fsPath;
}
return parts.slice(partIndex).join(sep);
}
/**
* Returns the `MetadataXml` info from a given file path. If the path is not a
* metadata xml file (-meta.xml), returns `undefined`.
*
* @param fsPath - File path to parse
* @returns MetadataXml info or undefined
*/
export function parseMetadataXml(fsPath: string): Optional<MetadataXml> {
const match = new RegExp(/(.+)\.(.+)-meta\.xml/).exec(basename(fsPath));
if (match) {
return { fullName: match[1], suffix: match[2], path: fsPath };
}
}
/**
* Returns the fullName for a nested metadata source file. This is for metadata
* types that can be nested more than 1 level such as report and reportFolder,
* dashboard and dashboardFolder, etc. It uses the directory name for the metadata type
* as the starting point (non-inclusively) to parse the fullName.
*
* Examples:
* (source format path)
* fsPath: force-app/main/default/reports/foo/bar/My_Report.report-meta.xml
* returns: foo/bar/My_Report
*
* (mdapi format path)
* fsPath: unpackaged/reports/foo/bar-meta.xml
* returns: foo/bar
*
* @param fsPath - File path to parse
* @param directoryName - name of directory to use as a parsing index
* @returns the FullName
*/
export function parseNestedFullName(fsPath: string, directoryName: string): string | undefined {
const pathSplits = fsPath.split(sep);
// Exit if the directoryName is not included in the file path.
if (!pathSplits.includes(directoryName)) {
return;
}
const pathPrefix = pathSplits.slice(pathSplits.lastIndexOf(directoryName) + 1);
pathPrefix[pathPrefix.length - 1] = (pathSplits.pop() as string).replace('-meta.xml', '').split('.')[0];
return pathPrefix.join('/');
}
export const calculateRelativePath =
(format: SfdxFileFormat) =>
(types: { self: MetadataType; parentType?: MetadataType }) =>
(fullName: string) =>
(fsPath: string): string => {
const base = format === 'source' ? DEFAULT_PACKAGE_ROOT_SFDX : '';
const { directoryName, suffix, inFolder, folderType, folderContentType } = types.self;
// if there isn't a suffix, assume this is a mixed content component that must
// reside in the directoryName of its type. trimUntil maintains the folder structure
// the file resides in for the new destination. This also applies to inFolder types:
// (report, dashboard, emailTemplate, document) and their folder container types:
// (reportFolder, dashboardFolder, emailFolder, documentFolder)
// It also applies to DigitalExperienceBundle types as we need to maintain the folder structure
if (
!suffix ||
Boolean(inFolder) ||
typeof folderContentType === 'string' ||
['digitalexperiencebundle', 'digitalexperience'].includes(types.self.id)
) {
return join(base, trimUntil(fsPath, directoryName, true));
}
if (folderType) {
// types like Territory2Model have child types inside them. We have to preserve those folder structures
if (types.parentType?.folderType && types.parentType?.folderType !== types.self.id) {
return join(base, trimUntil(fsPath, types.parentType.directoryName));
}
return join(base, directoryName, fullName.split('/')[0], basename(fsPath));
}
return join(base, directoryName, basename(fsPath));
};
/** (a)(b)=> a/b */
export const fnJoin =
(a: string) =>
(b: string): string =>
join(a, b);