-
Notifications
You must be signed in to change notification settings - Fork 965
Expand file tree
/
Copy pathcomponents-diff.ts
More file actions
121 lines (111 loc) · 5.09 KB
/
Copy pathcomponents-diff.ts
File metadata and controls
121 lines (111 loc) · 5.09 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
import chalk from 'chalk';
import tempy from 'tempy';
import { uniq } from 'lodash';
import type { ComponentID } from '@teambit/component-id';
import type { APIDiffResult } from '@teambit/semantics.entities.semantic-schema-diff';
import { diffFiles } from './diff-files';
import type { PathOsBased } from '@teambit/toolbox.path.path';
import type { SourceFile } from '@teambit/component.sources';
export type DiffStatus = 'MODIFIED' | 'UNCHANGED' | 'NEW' | 'DELETED';
export type FileDiff = {
filePath: string;
diffOutput: string;
status: DiffStatus;
fromContent: string;
toContent: string;
};
export type FieldsDiff = {
fieldName: string;
diffOutput: string;
};
export type DiffResults = {
id: ComponentID;
hasDiff: boolean;
filesDiff?: FileDiff[];
fieldsDiff?: FieldsDiff[] | null | undefined;
apiDiff?: APIDiffResult | null;
};
export type DiffOptions = {
verbose?: boolean; // whether show internal components diff, such as sourceRelativePath
formatDepsAsTable?: boolean; // show dependencies output as table
color?: boolean; // pass this option to git to return a colorful diff, default = true.
compareToParent?: boolean; // compare to the parent (previous) version
};
export async function getOneFileDiff(
filePathA: PathOsBased,
filePathB: PathOsBased,
fileALabel: string,
fileBLabel: string,
fileOrFieldName: string,
color = true
): Promise<string> {
const fileDiff = await diffFiles(filePathA, filePathB, color);
if (!fileDiff) return '';
const diffStartsString = '--- '; // the part before this string is not needed for our purpose
const diffStart = fileDiff.indexOf(diffStartsString);
if (!diffStart || diffStart < 1) return ''; // invalid diff
// e.g. Linux: --- a/private/var/folders/z ... .js
// Windows: --- "a/C:\\Users\\David\\AppData\\Local\\Temp\\bit ... .js
const regExpA = /--- ["]?a.*\n/; // exact "---", follow by a or "a (for Windows) then \n
const regExpB = /\+\+\+ ["]?b.*\n/; // exact "+++", follow by b or "b (for Windows) then \n
return fileDiff
.slice(diffStart)
.replace(regExpA, `--- ${fileOrFieldName} (${fileALabel})\n`)
.replace(regExpB, `+++ ${fileOrFieldName} (${fileBLabel})\n`);
}
export async function getFilesDiff(
filesA: SourceFile[],
filesB: SourceFile[],
filesAVersion: string,
filesBVersion: string,
fileNameAttribute = 'relative',
color = true
): Promise<FileDiff[]> {
const filesAPaths = filesA.map((f) => f[fileNameAttribute]);
const filesBPaths = filesB.map((f) => f[fileNameAttribute]);
const allPaths = uniq(filesAPaths.concat(filesBPaths));
const fileALabel = filesAVersion === filesBVersion ? `${filesAVersion} original` : filesAVersion;
const fileBLabel = filesAVersion === filesBVersion ? `${filesBVersion} modified` : filesBVersion;
const filesDiffP = allPaths.map(async (relativePath) => {
const getFileData = async (files: SourceFile[]): Promise<{ path: PathOsBased; content: string; hash?: string }> => {
const file = files.find((f) => f[fileNameAttribute] === relativePath);
const hash = file?.toSourceAsLinuxEOL().hash().hash;
const content = file ? file.contents : '';
const path = await tempy.write(content, { extension: 'js' });
return { path, content: content.toString('utf-8'), hash };
};
const [
{ path: fileAPath, content: fileAContent, hash: fileAHash },
{ path: fileBPath, content: fileBContent, hash: fileBHash },
] = await Promise.all([getFileData(filesA), getFileData(filesB)]);
// files are saved into the model with Linux EOL. if the current file has `/r/n` EOL, it'll show as modified
// unexpectedly. calculating the hash of the file with Linux EOL solves this issue.
const diffOutput =
fileAHash === fileBHash
? ''
: await getOneFileDiff(fileAPath, fileBPath, fileALabel, fileBLabel, relativePath, color);
let status: DiffStatus = 'UNCHANGED';
if (diffOutput && !fileAContent) status = 'NEW';
else if (diffOutput && !fileBContent) status = 'DELETED';
else if (diffOutput) status = 'MODIFIED';
return { filePath: relativePath, diffOutput, status, fromContent: fileAContent, toContent: fileBContent };
});
return Promise.all(filesDiffP);
}
export function outputDiffResults(diffResults: DiffResults[]): string {
return diffResults
.map((diffResult) => {
if (diffResult.hasDiff) {
const titleStr = `showing diff for ${chalk.bold(diffResult.id.toStringWithoutVersion())}`;
const titleSeparator = Array.from({ length: titleStr.length }).fill('-').join('');
const title = chalk.cyan(`${titleSeparator}\n${titleStr}\n${titleSeparator}`);
// @ts-ignore since hasDiff is true, filesDiff must be set
const filesWithDiff = diffResult.filesDiff.filter((file) => file.diffOutput);
const files = filesWithDiff.map((fileDiff) => fileDiff.diffOutput).join('\n');
const fields = diffResult.fieldsDiff ? diffResult.fieldsDiff.map((field) => field.diffOutput).join('\n') : '';
return `${title}\n${files}\n${fields}`;
}
return `no diff for ${chalk.bold(diffResult.id.toString())} (consider running with --verbose)`;
})
.join('\n\n');
}