@jayree/sfdx-plugin-manifest 2.9.0 → 3.0.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,640 @@
1
+ /*
2
+ * Copyright (c) 2022, jayree
3
+ * All rights reserved.
4
+ * Licensed under the BSD 3-Clause license.
5
+ * For full license text, see LICENSE.txt file in the repo root or https://opensource.org/licenses/BSD-3-Clause
6
+ */
7
+ import { join, basename, sep, posix, dirname, relative, resolve } from 'node:path';
8
+ import { fileURLToPath } from 'node:url';
9
+ import { format } from 'node:util';
10
+ import { SfCommand, Flags, arrayWithDeprecation } from '@salesforce/sf-plugins-core';
11
+ import { Messages, SfError, SfProject } from '@salesforce/core';
12
+ import { Args } from '@oclif/core';
13
+ import fs from 'fs-extra';
14
+ import { Logger, Listr } from 'listr2';
15
+ import { env } from '@salesforce/kit';
16
+ import { getString } from '@salesforce/ts-types';
17
+ import equal from 'fast-deep-equal';
18
+ import { ComponentSet, RegistryAccess, registry, VirtualTreeContainer, MetadataResolver, DestructiveChangesType, } from '@salesforce/source-deploy-retrieve';
19
+ import { parseMetadataXml } from '@salesforce/source-deploy-retrieve/lib/src/utils/index.js';
20
+ import Debug from 'debug';
21
+ import git from 'isomorphic-git';
22
+ // eslint-disable-next-line no-underscore-dangle
23
+ const __filename = fileURLToPath(import.meta.url);
24
+ // eslint-disable-next-line no-underscore-dangle
25
+ const __dirname = dirname(__filename);
26
+ Messages.importMessagesDirectory(__dirname);
27
+ const messages = Messages.loadMessages('@jayree/sfdx-plugin-manifest', 'gitdiff');
28
+ const logger = new Logger({ useIcons: false });
29
+ const debug = Debug('jayree:manifest:git:diff');
30
+ const registryAccess = new RegistryAccess();
31
+ export default class GitDiff extends SfCommand {
32
+ async run() {
33
+ const { flags, args } = await this.parse(GitDiff);
34
+ const sourcepath = flags['source-dir'];
35
+ this.destructiveChangesOnly = flags['destructive-changes-only'];
36
+ this.outputDir = flags['output-dir'];
37
+ this.projectRoot = this.project.getPath();
38
+ this.sourceApiVersion = (await this.getSourceApiVersion());
39
+ this.destructiveChanges = join(this.outputDir, 'destructiveChanges.xml');
40
+ this.manifest = join(this.outputDir, 'package.xml');
41
+ debug({
42
+ outputDir: this.outputDir,
43
+ projectRoot: this.projectRoot,
44
+ });
45
+ const isContentTypeJSON = env.getString('SFDX_CONTENT_TYPE', '').toUpperCase() === 'JSON';
46
+ this.isOutputEnabled = !(this.argv.find((arg) => arg === '--json') ?? isContentTypeJSON);
47
+ const gitArgs = await getGitArgsFromArgv(args.ref1, args.ref2, this.argv, this.projectRoot);
48
+ debug({ gitArgs });
49
+ const tasks = new Listr([
50
+ {
51
+ title: `Execute 'git --no-pager diff --name-status --no-renames ${gitArgs.refString}'`,
52
+ task: async (ctx, task) => {
53
+ const { gitlines, warnings } = await getGitDiff(gitArgs.ref1, gitArgs.ref2, this.projectRoot);
54
+ this.gitLines = gitlines;
55
+ this.outputWarnings = warnings;
56
+ task.output = `Changed files: ${this.gitLines.length}`;
57
+ },
58
+ options: { persistentOutput: true },
59
+ },
60
+ {
61
+ // title: 'Warning output',
62
+ skip: () => !this.outputWarnings?.length,
63
+ task: (ctx, task) => {
64
+ debug({ warnings: this.outputWarnings });
65
+ const moreWarnings = this.outputWarnings.splice(5);
66
+ for (const message of this.outputWarnings) {
67
+ task.output = `Warning: unstaged file ${message}`;
68
+ }
69
+ task.output = moreWarnings.length ? `... ${moreWarnings.length} more warnings` : '';
70
+ },
71
+ options: { persistentOutput: true, bottomBar: 6 },
72
+ },
73
+ {
74
+ title: 'Create virtual tree container',
75
+ skip: () => !this.gitLines.length,
76
+ task: (ctx, task) => task.newListr([
77
+ {
78
+ title: `ref1: ${gitArgs.ref1}`,
79
+ task: async () => {
80
+ this.ref1VirtualTreeContainer = await createVirtualTreeContainer(gitArgs.ref1, this.projectRoot, this.gitLines.filter((l) => l.status === 'M').map((l) => l.path));
81
+ },
82
+ },
83
+ {
84
+ title: gitArgs.ref2 !== '' ? `ref2: ${gitArgs.ref2}` : 'ref2: (staging area)',
85
+ task: async () => {
86
+ this.ref2VirtualTreeContainer = await createVirtualTreeContainer(gitArgs.ref2, this.projectRoot, this.gitLines.filter((l) => l.status === 'M').map((l) => l.path));
87
+ },
88
+ },
89
+ ], { concurrent: true }),
90
+ },
91
+ {
92
+ title: 'Analyze git diff results',
93
+ skip: () => !this.gitLines.length,
94
+ task: async (ctx, task) => {
95
+ if (sourcepath) {
96
+ this.fsPaths = sourcepath.map((filepath) => {
97
+ filepath = resolve(filepath);
98
+ if (!this.ref1VirtualTreeContainer.exists(filepath) &&
99
+ !this.ref2VirtualTreeContainer.exists(filepath)) {
100
+ throw new SfError(`The sourcepath "${filepath}" is not a valid source file path.`);
101
+ }
102
+ return filepath;
103
+ });
104
+ debug(`fsPaths: ${this.fsPaths.join(', ')}`);
105
+ }
106
+ const { manifest, output } = await getGitResults(this.gitLines, this.ref1VirtualTreeContainer, this.ref2VirtualTreeContainer, this.destructiveChangesOnly, this.fsPaths);
107
+ task.output = `Added: ${output.counts.added}, Deleted: ${output.counts.deleted}, Modified: ${output.counts.modified}, Unchanged: ${output.counts.unchanged}, Ignored: ${output.counts.ignored}${output.counts.error ? `, Errors: ${output.counts.error}` : ''}`;
108
+ this.outputErrors = output.errors;
109
+ debug({ manifest });
110
+ this.componentSet = fixComponentSetChilds(manifest);
111
+ this.componentSet.sourceApiVersion = this.sourceApiVersion;
112
+ },
113
+ options: { persistentOutput: true },
114
+ },
115
+ {
116
+ // title: 'Error output',
117
+ skip: () => !this.outputErrors?.length,
118
+ task: (ctx, task) => {
119
+ debug({ errors: this.outputErrors });
120
+ const moreErrors = this.outputErrors.splice(5);
121
+ for (const message of this.outputErrors) {
122
+ task.output = `Error: ${message}`;
123
+ }
124
+ task.output = moreErrors.length ? `... ${moreErrors.length} more errors` : '';
125
+ },
126
+ options: { persistentOutput: true, bottomBar: 6 },
127
+ },
128
+ {
129
+ title: 'Generate manifests',
130
+ skip: () => !this.componentSet?.size,
131
+ task: (ctx, task) => task.newListr([
132
+ {
133
+ title: this.manifest,
134
+ skip: () => !this.isOutputEnabled,
135
+ task: async () => {
136
+ await fs.ensureDir(dirname(this.manifest));
137
+ await fs.writeFile(this.manifest, await this.componentSet.getPackageXml());
138
+ },
139
+ options: { persistentOutput: true },
140
+ },
141
+ {
142
+ title: this.destructiveChanges,
143
+ skip: () => !this.componentSet.getTypesOfDestructiveChanges().length || !this.isOutputEnabled,
144
+ task: async () => {
145
+ await fs.ensureDir(dirname(this.destructiveChanges));
146
+ await fs.writeFile(this.destructiveChanges, await this.componentSet.getPackageXml(undefined, DestructiveChangesType.POST));
147
+ },
148
+ options: { persistentOutput: true },
149
+ },
150
+ ], { concurrent: true }),
151
+ },
152
+ ], {
153
+ rendererOptions: { showTimer: true, collapse: false, lazy: true, collapseErrors: false },
154
+ rendererSilent: !this.isOutputEnabled,
155
+ rendererFallback: debug.enabled,
156
+ });
157
+ try {
158
+ await tasks.run();
159
+ return {
160
+ destructiveChanges: await this.componentSet?.getObject(DestructiveChangesType.POST),
161
+ manifest: await this.componentSet?.getObject(),
162
+ };
163
+ }
164
+ catch (e) {
165
+ if (debug.enabled && this.isOutputEnabled) {
166
+ logger.fail(e.message);
167
+ }
168
+ throw e;
169
+ }
170
+ }
171
+ async getSourceApiVersion() {
172
+ const projectConfig = await this.project.resolveProjectConfig();
173
+ return getString(projectConfig, 'sourceApiVersion') ?? undefined;
174
+ }
175
+ }
176
+ GitDiff.summary = messages.getMessage('summary');
177
+ GitDiff.description = messages.getMessage('description');
178
+ GitDiff.examples = messages.getMessages('examples');
179
+ GitDiff.args = {
180
+ ref1: Args.string({
181
+ required: true,
182
+ description: messages.getMessage('args.ref1.description'),
183
+ }),
184
+ ref2: Args.string({
185
+ description: messages.getMessage('args.ref2.description'),
186
+ }),
187
+ };
188
+ GitDiff.requiresProject = true;
189
+ GitDiff.flags = {
190
+ 'source-dir': arrayWithDeprecation({
191
+ char: 'd',
192
+ summary: messages.getMessage('flags.source-dir.summary'),
193
+ description: messages.getMessage('flags.source-dir.description'),
194
+ deprecateAliases: true,
195
+ aliases: ['sourcepath', 'p'],
196
+ }),
197
+ 'output-dir': Flags.directory({
198
+ summary: messages.getMessage('flags.output-dir.summary'),
199
+ description: messages.getMessage('flags.output-dir.description'),
200
+ default: '',
201
+ deprecateAliases: true,
202
+ aliases: ['outputdir', 'o'],
203
+ }),
204
+ 'destructive-changes-only': Flags.boolean({
205
+ summary: messages.getMessage('flags.destructive-changes-only.summary'),
206
+ description: messages.getMessage('flags.destructive-changes-only.description'),
207
+ deprecateAliases: true,
208
+ aliases: ['destructivechangesonly'],
209
+ }),
210
+ };
211
+ async function resolveRef(refOrig, dir) {
212
+ if (refOrig === '') {
213
+ return '';
214
+ }
215
+ const getCommitLog = async (ref) => {
216
+ try {
217
+ const [log] = await git.log({
218
+ fs,
219
+ dir,
220
+ ref,
221
+ depth: 1,
222
+ });
223
+ return { oid: log.oid, parents: log.commit.parent };
224
+ }
225
+ catch (error) {
226
+ throw new Error(`ambiguous argument '${ref}': unknown revision or path not in the working tree.
227
+ See more help with --help`);
228
+ }
229
+ };
230
+ if (!['~', '^'].some((el) => refOrig.includes(el))) {
231
+ return (await getCommitLog(refOrig)).oid;
232
+ }
233
+ const firstIndex = [refOrig.indexOf('^'), refOrig.indexOf('~')]
234
+ .filter((a) => a >= 0)
235
+ .reduce((a, b) => Math.min(a, b));
236
+ let path = refOrig.substring(firstIndex);
237
+ let ref = refOrig.substring(0, firstIndex);
238
+ while (path.length && ref !== undefined) {
239
+ if (path.startsWith('^')) {
240
+ path = path.substring(1);
241
+ let next = Number(path.substring(0, 1));
242
+ path = next ? path.substring(1) : path;
243
+ next = next ? next : 1;
244
+ // eslint-disable-next-line no-await-in-loop
245
+ ref = (await getCommitLog(ref)).parents[next - 1];
246
+ }
247
+ else if (path.startsWith('~')) {
248
+ path = path.substring(1);
249
+ let next = Number(path.substring(0, 1));
250
+ path = next ? path.substring(1) : path;
251
+ next = next ? next : 1;
252
+ for (let index = 0; index <= next - 1; index++) {
253
+ // eslint-disable-next-line no-await-in-loop
254
+ ref = (await getCommitLog(ref)).parents[0];
255
+ }
256
+ }
257
+ else {
258
+ ref = undefined;
259
+ }
260
+ }
261
+ if (ref === undefined) {
262
+ throw new Error(`ambiguous argument '${refOrig}': unknown revision or path not in the working tree.`);
263
+ }
264
+ return ref;
265
+ }
266
+ async function getGitArgsFromArgv(ref1, ref2, argv, dir) {
267
+ const newArgv = [];
268
+ while (argv.length) {
269
+ let [e] = argv.splice(0, 1);
270
+ if (e.includes('=')) {
271
+ // skip parameter=value
272
+ }
273
+ else if (e.includes('-')) {
274
+ // remove value
275
+ if (argv[0] && !argv[0].includes('-') && ![ref1, ref2].includes(argv[0])) {
276
+ [e] = argv.splice(0, 1);
277
+ }
278
+ }
279
+ else {
280
+ newArgv.push(e);
281
+ }
282
+ }
283
+ argv = newArgv;
284
+ let refString = ref1;
285
+ const a = argv.join('.').split('.');
286
+ if ((a.length === 3 || a.length === 4) && typeof ref2 === 'undefined') {
287
+ ref1 = a[0];
288
+ ref2 = a[a.length - 1];
289
+ }
290
+ else if (a.length === 2 && typeof ref2 !== 'undefined') {
291
+ refString = `${ref1}..${ref2}`;
292
+ }
293
+ else if (a.length === 1) {
294
+ ref2 = '';
295
+ }
296
+ else {
297
+ throw new Error(`Ambiguous ${format('argument%s', argv.length === 1 ? '' : 's')}: ${argv.join(', ')}
298
+ See more help with --help`);
299
+ }
300
+ ref1 = await resolveRef(ref1, dir);
301
+ ref2 = await resolveRef(ref2, dir);
302
+ if (a.length === 4) {
303
+ ref1 = (await git.findMergeBase({
304
+ fs,
305
+ dir,
306
+ oids: [ref2, ref1],
307
+ }))[0];
308
+ }
309
+ return { ref1, ref2, refString };
310
+ }
311
+ function ensureOSPath(path) {
312
+ return path.split(posix.sep).join(sep);
313
+ }
314
+ function ensureGitRelPath(dir, path) {
315
+ return relative(dir, path).split(sep).join(posix.sep);
316
+ }
317
+ async function createVirtualTreeContainer(ref, dir, modifiedFiles) {
318
+ const paths = (await git.listFiles({ fs, dir, ref })).map((p) => join(dir, ensureOSPath(p)));
319
+ const oid = ref ? await git.resolveRef({ fs, dir, ref }) : '';
320
+ const virtualDirectoryByFullPath = new Map();
321
+ for await (const filename of paths) {
322
+ let dirPath = dirname(filename);
323
+ virtualDirectoryByFullPath.set(dirPath, {
324
+ dirPath,
325
+ children: Array.from(new Set(virtualDirectoryByFullPath.get(dirPath)?.children ?? []).add({
326
+ name: basename(filename),
327
+ data: parseMetadataXml(filename) && modifiedFiles.includes(filename)
328
+ ? oid
329
+ ? Buffer.from((await git.readBlob({ fs, dir, oid, filepath: ensureGitRelPath(dir, filename) })).blob)
330
+ : await fs.readFile(ensureOSPath(filename))
331
+ : Buffer.from(''),
332
+ })),
333
+ });
334
+ const splits = filename.split(sep);
335
+ for (let i = 1; i < splits.length - 1; i++) {
336
+ dirPath = splits.slice(0, i + 1).join(sep);
337
+ virtualDirectoryByFullPath.set(dirPath, {
338
+ dirPath,
339
+ children: Array.from(new Set(virtualDirectoryByFullPath.get(dirPath)?.children ?? []).add(splits[i + 1])),
340
+ });
341
+ }
342
+ }
343
+ return new VirtualTreeContainer(Array.from(virtualDirectoryByFullPath.values()));
344
+ }
345
+ async function analyzeFile(path, ref1VirtualTreeContainer, ref2VirtualTreeContainer) {
346
+ if (!parseMetadataXml(path)) {
347
+ return { path, status: 0 };
348
+ }
349
+ const ref2resolver = new MetadataResolver(registryAccess, ref2VirtualTreeContainer);
350
+ const [ref2Component] = ref2resolver.getComponentsFromPath(path); // git path only conaints files
351
+ const ref1resolver = new MetadataResolver(registryAccess, ref1VirtualTreeContainer);
352
+ const [ref1Component] = ref1resolver.getComponentsFromPath(path); // git path only conaints files
353
+ if (ref1resolver.forceIgnoredPaths.has(path) || ref2resolver.forceIgnoredPaths.has(path)) {
354
+ return { path, status: -2 };
355
+ }
356
+ if (equal(await ref1Component.parseXml(), await ref2Component.parseXml())) {
357
+ return { path, status: -1 };
358
+ }
359
+ if (ref1Component.type.strictDirectoryName === true || !ref1Component.type.children) {
360
+ return { path, status: 0 };
361
+ }
362
+ const ref2ChildUniqueIdArray = ref2Component
363
+ .getChildren()
364
+ .map((childComponent) => getUniqueIdentifier(childComponent));
365
+ const ref1ChildUniqueIdArray = ref1Component
366
+ .getChildren()
367
+ .map((childComponent) => getUniqueIdentifier(childComponent));
368
+ const childComponentsNotInRef2 = ref1Component
369
+ .getChildren()
370
+ .filter((childComponent) => !ref2ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // deleted
371
+ const childComponentsNotInRef1 = ref2Component
372
+ .getChildren()
373
+ .filter((childComponent) => !ref1ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // added
374
+ const childComponentsInRef1AndRef2 = ref1Component
375
+ .getChildren()
376
+ .filter((childComponent) => ref2ChildUniqueIdArray.includes(getUniqueIdentifier(childComponent))); // modified?
377
+ debug({ childComponentsNotInRef2, childComponentsNotInRef1, childComponentsInRef1AndRef2 });
378
+ for await (const childComponentRef1 of childComponentsInRef1AndRef2) {
379
+ const [childComponentRef2] = ref2Component
380
+ .getChildren()
381
+ .filter((childComponent) => getUniqueIdentifier(childComponentRef1) === getUniqueIdentifier(childComponent));
382
+ if (!equal(await childComponentRef1.parseXml(), await childComponentRef2.parseXml())) {
383
+ childComponentsNotInRef1.push(childComponentRef2); // modified! -> add to added
384
+ }
385
+ }
386
+ debug({ childComponentsNotInRef1 });
387
+ return {
388
+ path,
389
+ status: 1 + childComponentsNotInRef2.length + childComponentsNotInRef1.length,
390
+ toManifest: childComponentsNotInRef1,
391
+ toDestructiveChanges: childComponentsNotInRef2,
392
+ };
393
+ }
394
+ function getUniqueIdentifier(component) {
395
+ return `${component.type.name}#${getString(component, component.type.uniqueIdElement)}`;
396
+ }
397
+ async function getFileStateChanges(commitHash1, commitHash2, dir) {
398
+ // eslint-disable-next-line @typescript-eslint/no-unsafe-return
399
+ return git.walk({
400
+ fs,
401
+ dir,
402
+ trees: [git.TREE({ ref: commitHash1 }), git.TREE({ ref: commitHash2 })],
403
+ async map(filepath, [A, B]) {
404
+ if (filepath === '.' || (await A?.type()) === 'tree' || (await B?.type()) === 'tree') {
405
+ return;
406
+ }
407
+ const Aoid = await A?.oid();
408
+ const Boid = await B?.oid();
409
+ let type = 'EQ';
410
+ if (Aoid !== Boid) {
411
+ type = 'M';
412
+ }
413
+ if (Aoid === undefined) {
414
+ type = 'A';
415
+ }
416
+ if (Boid === undefined) {
417
+ type = 'D';
418
+ }
419
+ if (type !== 'EQ') {
420
+ return {
421
+ path: join(dir, ensureOSPath(filepath)),
422
+ status: type,
423
+ };
424
+ }
425
+ },
426
+ });
427
+ }
428
+ async function getStatusMatrix(dir, ref) {
429
+ const getStatus = (row) => {
430
+ if ([
431
+ [0, 2, 2],
432
+ [0, 2, 3], // added, staged, with unstaged changes
433
+ ].some((a) => a.every((val, index) => val === row[index]))) {
434
+ return 'A';
435
+ }
436
+ if ([
437
+ [1, 0, 0],
438
+ [1, 0, 1],
439
+ [1, 1, 0],
440
+ [1, 2, 0],
441
+ [1, 0, 3], // modified, staged, with unstaged deletion
442
+ ].some((a) => a.every((val, index) => val === row[index]))) {
443
+ return 'D';
444
+ }
445
+ if ([
446
+ [1, 2, 1],
447
+ [1, 2, 2],
448
+ [1, 2, 3], // modified, staged, with unstaged changes
449
+ ].some((a) => a.every((val, index) => val === row[index]))) {
450
+ return 'M';
451
+ }
452
+ return undefined;
453
+ };
454
+ const statusMatrix = await git.statusMatrix({ fs, dir, ref });
455
+ const warnings = statusMatrix
456
+ .filter((row) => [
457
+ [0, 2, 0],
458
+ [0, 0, 3],
459
+ [0, 2, 3],
460
+ [1, 2, 1],
461
+ [1, 0, 3],
462
+ [1, 1, 3],
463
+ [1, 2, 3],
464
+ [1, 1, 0],
465
+ [1, 2, 0],
466
+ [1, 0, 1], // deleted, unstaged
467
+ ].some((a) => a.every((val, index) => val === row.slice(1)[index])))
468
+ .map((row) => join(dir, ensureOSPath(row[0])));
469
+ const gitlines = statusMatrix
470
+ .filter((row) => ![
471
+ [0, 0, 0],
472
+ [1, 1, 1],
473
+ [0, 0, 3],
474
+ [0, 2, 0],
475
+ [1, 1, 3], // modified, staged, with unstaged original file
476
+ ].some((a) => a.every((val, index) => val === row.slice(1)[index])))
477
+ .map((row) => ({
478
+ path: join(dir, ensureOSPath(row[0])),
479
+ status: getStatus(row.slice(1)),
480
+ }));
481
+ return { warnings, lines: gitlines };
482
+ }
483
+ async function getGitDiff(ref1, ref2, dir) {
484
+ let gitlines;
485
+ let warnings = [];
486
+ const proj = await SfProject.resolve();
487
+ const resolveSourcePaths = proj.getUniquePackageDirectories().map((pDir) => pDir.fullPath);
488
+ if (ref2) {
489
+ gitlines = (await getFileStateChanges(ref1, ref2, dir)).filter((l) => resolveSourcePaths.some((f) => l.path.startsWith(f)));
490
+ }
491
+ else {
492
+ const { warnings: warn, lines } = await getStatusMatrix(dir, ref1);
493
+ warnings = warn.filter((l) => resolveSourcePaths.some((f) => l.startsWith(f)));
494
+ gitlines = lines.filter((l) => resolveSourcePaths.some((f) => l.path.startsWith(f)));
495
+ }
496
+ gitlines = gitlines.filter((line) => {
497
+ if (line.status === 'D') {
498
+ for (const sourcePath of resolveSourcePaths) {
499
+ const defaultFolder = join(sourcePath, 'main', 'default');
500
+ const filePath = line.path.replace(line.path.startsWith(defaultFolder) ? defaultFolder : sourcePath, '');
501
+ const target = gitlines.find((t) => t.path.endsWith(filePath) && t.status === 'A');
502
+ if (target) {
503
+ debug(`rename: ${line.path} -> ${target.path}`);
504
+ return false;
505
+ }
506
+ }
507
+ }
508
+ return true;
509
+ });
510
+ debug({ gitlines, warnings });
511
+ return { gitlines, warnings };
512
+ }
513
+ // eslint-disable-next-line complexity
514
+ async function getGitResults(gitLines, ref1VirtualTreeContainer, ref2VirtualTreeContainer, destructiveChangesOnly, fsPaths) {
515
+ const results = {
516
+ manifest: new ComponentSet(undefined, registryAccess),
517
+ output: {
518
+ unchanged: [],
519
+ ignored: { ref1: [], ref2: [] },
520
+ counts: { added: 0, deleted: 0, modified: 0, unchanged: 0, ignored: 0, error: 0 },
521
+ errors: [],
522
+ },
523
+ };
524
+ const ref1Resolver = new MetadataResolver(registryAccess, ref1VirtualTreeContainer);
525
+ const ref2Resolver = new MetadataResolver(registryAccess, ref2VirtualTreeContainer);
526
+ const getComponentsFromPath = (resolver, path) => {
527
+ let result = [];
528
+ try {
529
+ result = resolver.getComponentsFromPath(path);
530
+ }
531
+ catch (error) {
532
+ results.output.counts.error++;
533
+ results.output.errors.push(error.message);
534
+ }
535
+ return result;
536
+ };
537
+ const analyzedFilesPromises = [];
538
+ for (const [, { status, path }] of gitLines.entries()) {
539
+ if (!fsPaths || fsPaths.some((fsPath) => resolve(path).startsWith(fsPath))) {
540
+ if (status === 'D') {
541
+ for (const c of getComponentsFromPath(ref1Resolver, path)) {
542
+ if (c.xml === path || gitLines.find((x) => x.path === c.xml)) {
543
+ results.manifest.add(c, DestructiveChangesType.POST);
544
+ results.output.counts.deleted++;
545
+ }
546
+ else {
547
+ try {
548
+ if (c.xml) {
549
+ // in case a binary source file of a bundle was deleted, check if the bundle ist still valid and update instead of delete
550
+ ref2Resolver.getComponentsFromPath(c.xml);
551
+ }
552
+ if (!destructiveChangesOnly) {
553
+ results.manifest.add(c);
554
+ results.output.counts.added++;
555
+ }
556
+ }
557
+ catch (error) {
558
+ results.output.counts.error++;
559
+ results.output.errors.push(error.message);
560
+ }
561
+ }
562
+ }
563
+ }
564
+ else if (status === 'A') {
565
+ if (!destructiveChangesOnly) {
566
+ for (const c of getComponentsFromPath(ref2Resolver, path)) {
567
+ results.manifest.add(c);
568
+ results.output.counts.added++;
569
+ }
570
+ }
571
+ }
572
+ else {
573
+ analyzedFilesPromises.push(analyzeFile(path, ref1VirtualTreeContainer, ref2VirtualTreeContainer));
574
+ }
575
+ }
576
+ else {
577
+ debug(`${path} not included in sourcepath`);
578
+ }
579
+ }
580
+ for await (const check of analyzedFilesPromises) {
581
+ if (check.status === 0) {
582
+ if (!destructiveChangesOnly) {
583
+ for (const c of getComponentsFromPath(ref2Resolver, check.path)) {
584
+ results.manifest.add(c);
585
+ results.output.counts.modified++;
586
+ }
587
+ }
588
+ }
589
+ else if (check.status === -1) {
590
+ results.output.unchanged.push(check.path);
591
+ results.output.counts.unchanged++;
592
+ }
593
+ else if (check.status === -2) {
594
+ results.output.counts.ignored++;
595
+ results.output.ignored.ref2.push(check.path);
596
+ }
597
+ else {
598
+ if ((check.toDestructiveChanges && check.toDestructiveChanges.length > 0) ||
599
+ (check.toManifest && check.toManifest.length > 0 && !destructiveChangesOnly)) {
600
+ results.output.counts.modified++;
601
+ }
602
+ if (check.toDestructiveChanges) {
603
+ for (const c of check.toDestructiveChanges) {
604
+ results.manifest.add(c, DestructiveChangesType.POST);
605
+ }
606
+ }
607
+ if (!destructiveChangesOnly && check.toManifest) {
608
+ for (const c of check.toManifest) {
609
+ results.manifest.add(c);
610
+ }
611
+ }
612
+ }
613
+ }
614
+ results.output.ignored = {
615
+ ref1: Array.from(ref1Resolver.forceIgnoredPaths),
616
+ ref2: results.output.ignored.ref2.concat(Array.from(ref2Resolver.forceIgnoredPaths)),
617
+ };
618
+ results.output.counts.ignored =
619
+ results.output.counts.ignored + ref1Resolver.forceIgnoredPaths.size + ref2Resolver.forceIgnoredPaths.size;
620
+ return results;
621
+ }
622
+ function fixComponentSetChilds(cs) {
623
+ let sourceComponents = cs.getSourceComponents();
624
+ // SDR library is more strict and avoids fixes like this
625
+ const childsTobeReplacedByParent = [
626
+ ...Object.keys(registry.types.workflow.children?.types ?? {}),
627
+ ...Object.keys(registry.types.sharingrules.children?.types ?? {}),
628
+ ...Object.keys(registry.types.customobjecttranslation.children?.types ?? {}),
629
+ ...Object.keys(registry.types.bot.children?.types ?? {}),
630
+ ];
631
+ sourceComponents = sourceComponents.map((component) => {
632
+ if (!component.isMarkedForDelete() && childsTobeReplacedByParent.includes(component.type.id) && component.parent) {
633
+ debug(`replace: ${component.type.name}:${component.fullName} -> ${component.parent.type.name}:${component.parent.fullName}`);
634
+ return component.parent;
635
+ }
636
+ return component;
637
+ });
638
+ return new ComponentSet(sourceComponents, registryAccess);
639
+ }
640
+ //# sourceMappingURL=diff.js.map