This repository was archived by the owner on Mar 25, 2026. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 21
Expand file tree
/
Copy pathtrackingFunctions.ts
More file actions
154 lines (143 loc) · 4.67 KB
/
Copy pathtrackingFunctions.ts
File metadata and controls
154 lines (143 loc) · 4.67 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
/*
* 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 path from 'node:path';
import { ChangeResult, SourceTracking, SourceTrackingOptions } from '@salesforce/source-tracking';
import { Messages, SfError } from '@salesforce/core';
import {
ComponentSet,
ComponentStatus,
DeployResult,
FileResponse,
RetrieveResult,
} from '@salesforce/source-deploy-retrieve';
import { Ux } from '@salesforce/sf-plugins-core';
Messages.importMessagesDirectoryFromMetaUrl(import.meta.url);
const messages = Messages.loadMessages('@salesforce/plugin-source', 'tracking');
type TrackingSetupRequest = {
ignoreConflicts: boolean;
ux: Ux;
} & SourceTrackingOptions;
type TrackingUpdateRequest = {
tracking: SourceTracking;
result: DeployResult | RetrieveResult;
ux: Ux;
/**
* We don't want to get the fileResponses if there have been deletes (SDR will throw)
* You can also pass this in if your command already ran getFileResponses and you want to avoid the perf hit from doing it twice
*/
fileResponses?: FileResponse[];
};
type ConflictResponse = {
state: 'Conflict';
fullName: string;
type: string;
filePath: string;
};
/**
* Check if any conflicts exist in a specific component set.
* If conflicts exist, this will output the table and throw
*/
export const filterConflictsByComponentSet = async ({
tracking,
components,
ux,
}: {
tracking: SourceTracking;
components: ComponentSet;
ux: Ux;
}): Promise<ChangeResult[]> => {
const filteredConflicts = (await tracking.getConflicts()).filter((cr) =>
components.has({ fullName: cr.name as string, type: cr.type as string })
);
processConflicts(filteredConflicts, ux, messages.getMessage('conflictMsg'));
return filteredConflicts;
};
/**
* Init SourceTracking (STL) and do conflict detection
*
* @param options
* @returns SourceTracking
*/
export const trackingSetup = async (options: TrackingSetupRequest): Promise<SourceTracking> => {
const { ux, org, ignoreConflicts, ...createOptions } = options;
const tracking = await SourceTracking.create({ org, ...createOptions });
if (!ignoreConflicts) {
processConflicts(await tracking.getConflicts(), ux, messages.getMessage('conflictMsg'));
}
return tracking;
};
/**
* Shared function for taking a Deploy/Retrieve result and handle the source tracking updates
*
* @param options
*/
export const updateTracking = async ({ tracking, result, ux, fileResponses }: TrackingUpdateRequest): Promise<void> => {
// might not exist if we exited from the operation early
if (!result) {
return;
}
ux.spinner.start('Updating source tracking');
const successes = (fileResponses ?? result.getFileResponses()).filter(
(fileResponse) => fileResponse.state !== ComponentStatus.Failed
);
if (!successes.length) {
ux.spinner.stop();
return;
}
await Promise.all([
tracking.updateLocalTracking({
files: successes
.filter((fileResponse) => fileResponse.state !== ComponentStatus.Deleted)
.map((fileResponse) => fileResponse.filePath),
deletedFiles: successes
.filter((fileResponse) => fileResponse.state === ComponentStatus.Deleted)
.map((fileResponse) => fileResponse.filePath),
}),
tracking.updateRemoteTracking(
successes.map(({ state, fullName, type, filePath }) => ({ state, fullName, type, filePath })),
result instanceof RetrieveResult
),
]);
ux.spinner.stop();
};
const writeConflictTable = (conflicts: ConflictResponse[], ux: Ux): void => {
ux.table(conflicts, {
state: { header: 'STATE' },
fullName: { header: 'FULL NAME' },
type: { header: 'TYPE' },
filePath: { header: 'FILE PATH' },
});
};
/**
* Write a table (if not json) and throw an error that includes a custom message and the conflict data
*
* @param conflicts
* @param ux
* @param message
*/
const processConflicts = (conflicts: ChangeResult[], ux: Ux, message: string): void => {
if (conflicts.length === 0) {
return;
}
// map do dedupe by name-type-filename
const conflictMap = new Map<string, ConflictResponse>();
conflicts.forEach((c) => {
c.filenames?.forEach((f) => {
conflictMap.set(`${c.name ?? ''}#${c.type ?? ''}#${f}`, {
state: 'Conflict',
fullName: c.name as string,
type: c.type as string,
filePath: path.resolve(f),
});
});
});
const reformattedConflicts = Array.from(conflictMap.values());
writeConflictTable(reformattedConflicts, ux);
const err = new SfError(message, 'sourceConflictDetected');
err.setData(reformattedConflicts);
throw err;
};