@code-pushup/ci 0.52.0 → 0.53.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/index.js +438 -430
- package/package.json +3 -3
- package/src/index.d.ts +2 -0
- package/src/lib/monorepo/index.d.ts +1 -1
- package/src/lib/monorepo/tools.d.ts +1 -0
package/index.js
CHANGED
|
@@ -1,7 +1,9 @@
|
|
|
1
|
-
// packages/ci/src/lib/
|
|
2
|
-
import
|
|
3
|
-
import
|
|
4
|
-
|
|
1
|
+
// packages/ci/src/lib/monorepo/list-projects.ts
|
|
2
|
+
import { glob as glob2 } from "glob";
|
|
3
|
+
import { join as join7 } from "node:path";
|
|
4
|
+
|
|
5
|
+
// packages/ci/src/lib/monorepo/handlers/npm.ts
|
|
6
|
+
import { join as join2 } from "node:path";
|
|
5
7
|
|
|
6
8
|
// packages/models/src/lib/implementation/schemas.ts
|
|
7
9
|
import { MATERIAL_ICONS } from "vscode-material-icons";
|
|
@@ -851,445 +853,74 @@ import { MarkdownDocument as MarkdownDocument4, md as md5 } from "build-md";
|
|
|
851
853
|
// packages/utils/src/lib/reports/log-stdout-summary.ts
|
|
852
854
|
import { bold as bold4, cyan, cyanBright, green as green2, red } from "ansis";
|
|
853
855
|
|
|
854
|
-
// packages/ci/src/lib/
|
|
855
|
-
import
|
|
856
|
-
|
|
857
|
-
|
|
858
|
-
|
|
859
|
-
|
|
860
|
-
|
|
861
|
-
`--persist.outputDir=${path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR)}`,
|
|
862
|
-
`--persist.filename=${createFilename(project)}`,
|
|
863
|
-
...DEFAULT_PERSIST_FORMAT.map((format) => `--persist.format=${format}`)
|
|
864
|
-
];
|
|
865
|
-
}
|
|
866
|
-
function persistedCliFiles({
|
|
867
|
-
directory,
|
|
868
|
-
isDiff,
|
|
869
|
-
project,
|
|
870
|
-
formats
|
|
871
|
-
}) {
|
|
872
|
-
const rootDir = path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR);
|
|
873
|
-
const filename = isDiff ? `${createFilename(project)}-diff` : createFilename(project);
|
|
874
|
-
const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
|
|
875
|
-
(acc, format) => ({
|
|
876
|
-
...acc,
|
|
877
|
-
[`${format}FilePath`]: path.join(rootDir, `${filename}.${format}`)
|
|
878
|
-
}),
|
|
879
|
-
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
|
|
880
|
-
{}
|
|
856
|
+
// packages/ci/src/lib/monorepo/packages.ts
|
|
857
|
+
import { glob } from "glob";
|
|
858
|
+
import { basename, dirname, join } from "node:path";
|
|
859
|
+
async function listPackages(cwd, patterns = ["**"]) {
|
|
860
|
+
const files = await glob(
|
|
861
|
+
patterns.map((pattern) => pattern.replace(/\/?$/, "/package.json")),
|
|
862
|
+
{ cwd }
|
|
881
863
|
);
|
|
882
|
-
|
|
864
|
+
return Promise.all(
|
|
865
|
+
files.toSorted().map(async (file) => {
|
|
866
|
+
const packageJson = await readJsonFile(join(cwd, file));
|
|
867
|
+
const directory = join(cwd, dirname(file));
|
|
868
|
+
const name = packageJson.name || basename(directory);
|
|
869
|
+
return { name, directory, packageJson };
|
|
870
|
+
})
|
|
871
|
+
);
|
|
872
|
+
}
|
|
873
|
+
async function listWorkspaces(cwd) {
|
|
874
|
+
const rootPackageJson = await readRootPackageJson(cwd);
|
|
875
|
+
const patterns = Array.isArray(rootPackageJson.workspaces) ? rootPackageJson.workspaces : rootPackageJson.workspaces?.packages;
|
|
883
876
|
return {
|
|
884
|
-
|
|
885
|
-
|
|
886
|
-
rootDir,
|
|
887
|
-
files
|
|
888
|
-
}
|
|
877
|
+
workspaces: await listPackages(cwd, patterns),
|
|
878
|
+
rootPackageJson
|
|
889
879
|
};
|
|
890
880
|
}
|
|
891
|
-
function
|
|
892
|
-
|
|
893
|
-
|
|
894
|
-
|
|
895
|
-
const prefix = projectToFilename(project);
|
|
896
|
-
return `${prefix}-${DEFAULT_PERSIST_FILENAME}`;
|
|
897
|
-
}
|
|
898
|
-
|
|
899
|
-
// packages/ci/src/lib/cli/commands/collect.ts
|
|
900
|
-
async function runCollect({
|
|
901
|
-
bin,
|
|
902
|
-
config,
|
|
903
|
-
directory,
|
|
904
|
-
silent,
|
|
905
|
-
project
|
|
906
|
-
}) {
|
|
907
|
-
const { stdout } = await executeProcess({
|
|
908
|
-
command: bin,
|
|
909
|
-
args: [
|
|
910
|
-
...config ? [`--config=${config}`] : [],
|
|
911
|
-
...persistCliOptions({ directory, project })
|
|
912
|
-
],
|
|
913
|
-
cwd: directory
|
|
914
|
-
});
|
|
915
|
-
if (!silent) {
|
|
916
|
-
console.info(stdout);
|
|
881
|
+
async function hasWorkspacesEnabled(cwd) {
|
|
882
|
+
const packageJson = await readRootPackageJson(cwd);
|
|
883
|
+
if (!packageJson.private) {
|
|
884
|
+
return false;
|
|
917
885
|
}
|
|
918
|
-
|
|
919
|
-
|
|
920
|
-
|
|
921
|
-
// packages/ci/src/lib/cli/commands/compare.ts
|
|
922
|
-
async function runCompare({ before, after, label }, { bin, config, directory, silent, project }) {
|
|
923
|
-
const { stdout } = await executeProcess({
|
|
924
|
-
command: bin,
|
|
925
|
-
args: [
|
|
926
|
-
"compare",
|
|
927
|
-
`--before=${before}`,
|
|
928
|
-
`--after=${after}`,
|
|
929
|
-
...label ? [`--label=${label}`] : [],
|
|
930
|
-
...config ? [`--config=${config}`] : [],
|
|
931
|
-
...persistCliOptions({ directory, project })
|
|
932
|
-
],
|
|
933
|
-
cwd: directory
|
|
934
|
-
});
|
|
935
|
-
if (!silent) {
|
|
936
|
-
console.info(stdout);
|
|
886
|
+
if (Array.isArray(packageJson.workspaces)) {
|
|
887
|
+
return packageJson.workspaces.length > 0;
|
|
937
888
|
}
|
|
938
|
-
|
|
939
|
-
|
|
940
|
-
|
|
941
|
-
// packages/ci/src/lib/cli/commands/merge-diffs.ts
|
|
942
|
-
async function runMergeDiffs(files, { bin, config, directory, silent }) {
|
|
943
|
-
const { stdout } = await executeProcess({
|
|
944
|
-
command: bin,
|
|
945
|
-
args: [
|
|
946
|
-
"merge-diffs",
|
|
947
|
-
...files.map((file) => `--files=${file}`),
|
|
948
|
-
...config ? [`--config=${config}`] : [],
|
|
949
|
-
...persistCliOptions({ directory })
|
|
950
|
-
],
|
|
951
|
-
cwd: directory
|
|
952
|
-
});
|
|
953
|
-
if (!silent) {
|
|
954
|
-
console.info(stdout);
|
|
889
|
+
if (typeof packageJson.workspaces === "object") {
|
|
890
|
+
return Boolean(packageJson.workspaces.packages?.length);
|
|
955
891
|
}
|
|
956
|
-
return
|
|
892
|
+
return false;
|
|
957
893
|
}
|
|
958
|
-
|
|
959
|
-
|
|
960
|
-
async function runPrintConfig({
|
|
961
|
-
bin,
|
|
962
|
-
config,
|
|
963
|
-
directory,
|
|
964
|
-
silent
|
|
965
|
-
}) {
|
|
966
|
-
const { stdout } = await executeProcess({
|
|
967
|
-
command: bin,
|
|
968
|
-
args: [...config ? [`--config=${config}`] : [], "print-config"],
|
|
969
|
-
cwd: directory
|
|
970
|
-
});
|
|
971
|
-
if (!silent) {
|
|
972
|
-
console.info(stdout);
|
|
973
|
-
}
|
|
894
|
+
async function readRootPackageJson(cwd) {
|
|
895
|
+
return await readJsonFile(join(cwd, "package.json"));
|
|
974
896
|
}
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
|
|
978
|
-
return {
|
|
979
|
-
project: project?.name,
|
|
980
|
-
bin: project?.bin ?? settings.bin,
|
|
981
|
-
directory: project?.directory ?? settings.directory,
|
|
982
|
-
config: settings.config,
|
|
983
|
-
silent: settings.silent
|
|
984
|
-
};
|
|
897
|
+
function hasDependency(packageJson, name) {
|
|
898
|
+
const { dependencies = {}, devDependencies = {} } = packageJson;
|
|
899
|
+
return name in devDependencies || name in dependencies;
|
|
985
900
|
}
|
|
986
|
-
|
|
987
|
-
|
|
988
|
-
|
|
989
|
-
async function commentOnPR(mdPath, api, logger) {
|
|
990
|
-
const markdown = await readFile2(mdPath, "utf8");
|
|
991
|
-
const identifier = `<!-- generated by @code-pushup/ci -->`;
|
|
992
|
-
const body = truncateBody(
|
|
993
|
-
`${markdown}
|
|
994
|
-
|
|
995
|
-
${identifier}
|
|
996
|
-
`,
|
|
997
|
-
api.maxCommentChars,
|
|
998
|
-
logger
|
|
999
|
-
);
|
|
1000
|
-
const comments = await api.listComments();
|
|
1001
|
-
logger.debug(`Fetched ${comments.length} comments for pull request`);
|
|
1002
|
-
const prevComment = comments.find(
|
|
1003
|
-
(comment) => comment.body.includes(identifier)
|
|
1004
|
-
);
|
|
1005
|
-
logger.debug(
|
|
1006
|
-
prevComment ? `Found previous comment ${prevComment.id} from Code PushUp` : "Previous Code PushUp comment not found"
|
|
1007
|
-
);
|
|
1008
|
-
if (prevComment) {
|
|
1009
|
-
const updatedComment = await api.updateComment(prevComment.id, body);
|
|
1010
|
-
logger.info(`Updated body of comment ${updatedComment.url}`);
|
|
1011
|
-
return updatedComment.id;
|
|
1012
|
-
}
|
|
1013
|
-
const createdComment = await api.createComment(body);
|
|
1014
|
-
logger.info(`Created new comment ${createdComment.url}`);
|
|
1015
|
-
return createdComment.id;
|
|
901
|
+
function hasScript(packageJson, script) {
|
|
902
|
+
const { scripts = {} } = packageJson;
|
|
903
|
+
return script in scripts;
|
|
1016
904
|
}
|
|
1017
|
-
function
|
|
1018
|
-
|
|
1019
|
-
if (body.length > max) {
|
|
1020
|
-
logger.warn(`Comment body is too long. Truncating to ${max} characters.`);
|
|
1021
|
-
return body.slice(0, max - truncateWarning.length) + truncateWarning;
|
|
1022
|
-
}
|
|
1023
|
-
return body;
|
|
905
|
+
function hasCodePushUpDependency(packageJson) {
|
|
906
|
+
return hasDependency(packageJson, "@code-pushup/cli");
|
|
1024
907
|
}
|
|
1025
908
|
|
|
1026
|
-
// packages/ci/src/lib/
|
|
1027
|
-
var
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1031
|
-
|
|
1032
|
-
|
|
1033
|
-
|
|
1034
|
-
|
|
1035
|
-
|
|
1036
|
-
|
|
1037
|
-
|
|
1038
|
-
}
|
|
1039
|
-
|
|
1040
|
-
|
|
1041
|
-
import { DiffNameStatus, simpleGit as simpleGit3 } from "simple-git";
|
|
1042
|
-
async function listChangedFiles(refs, git = simpleGit3()) {
|
|
1043
|
-
const statuses = [
|
|
1044
|
-
DiffNameStatus.ADDED,
|
|
1045
|
-
DiffNameStatus.COPIED,
|
|
1046
|
-
DiffNameStatus.MODIFIED,
|
|
1047
|
-
DiffNameStatus.RENAMED
|
|
1048
|
-
];
|
|
1049
|
-
const { files } = await git.diffSummary([
|
|
1050
|
-
refs.base,
|
|
1051
|
-
refs.head,
|
|
1052
|
-
`--diff-filter=${statuses.join("")}`,
|
|
1053
|
-
"--find-renames",
|
|
1054
|
-
"--find-copies"
|
|
1055
|
-
]);
|
|
1056
|
-
const entries = await Promise.all(
|
|
1057
|
-
files.filter(({ binary }) => !binary).map(({ file }) => {
|
|
1058
|
-
const rename = parseFileRename(file);
|
|
1059
|
-
if (rename) {
|
|
1060
|
-
return { file: rename.curr, originalFile: rename.prev };
|
|
1061
|
-
}
|
|
1062
|
-
return { file };
|
|
1063
|
-
}).map(async ({ file, originalFile }) => {
|
|
1064
|
-
const diff = await git.diff([
|
|
1065
|
-
"--unified=0",
|
|
1066
|
-
refs.base,
|
|
1067
|
-
refs.head,
|
|
1068
|
-
"--",
|
|
1069
|
-
file,
|
|
1070
|
-
...originalFile ? [originalFile] : []
|
|
1071
|
-
]);
|
|
1072
|
-
const lineChanges = parseDiff(diff);
|
|
1073
|
-
return [
|
|
1074
|
-
file,
|
|
1075
|
-
{ ...originalFile && { originalFile }, lineChanges }
|
|
1076
|
-
];
|
|
1077
|
-
})
|
|
1078
|
-
);
|
|
1079
|
-
return Object.fromEntries(entries);
|
|
1080
|
-
}
|
|
1081
|
-
function parseFileRename(file) {
|
|
1082
|
-
const partialRenameMatch = file.match(/^(.*){(.*) => (.*)}(.*)$/);
|
|
1083
|
-
if (partialRenameMatch) {
|
|
1084
|
-
const [, prefix = "", prev, curr, suffix] = partialRenameMatch;
|
|
1085
|
-
return {
|
|
1086
|
-
prev: prefix + prev + suffix,
|
|
1087
|
-
curr: prefix + curr + suffix
|
|
1088
|
-
};
|
|
1089
|
-
}
|
|
1090
|
-
const fullRenameMatch = file.match(/^(.*) => (.*)$/);
|
|
1091
|
-
if (fullRenameMatch) {
|
|
1092
|
-
const [, prev = "", curr = ""] = fullRenameMatch;
|
|
1093
|
-
return { prev, curr };
|
|
1094
|
-
}
|
|
1095
|
-
return null;
|
|
1096
|
-
}
|
|
1097
|
-
function parseDiff(diff) {
|
|
1098
|
-
const changeSummaries = diff.match(/@@ [ \d,+-]+ @@/g);
|
|
1099
|
-
if (changeSummaries == null) {
|
|
1100
|
-
return [];
|
|
1101
|
-
}
|
|
1102
|
-
return changeSummaries.map((summary) => summary.match(/^@@ -(\d+|\d+,\d+) \+(\d+|\d+,\d+) @@$/)).filter((matches) => matches != null).map((matches) => {
|
|
1103
|
-
const [prevLine = "", prevAdded = "1"] = matches[1].split(",");
|
|
1104
|
-
const [currLine = "", currAdded = "1"] = matches[2].split(",");
|
|
1105
|
-
return {
|
|
1106
|
-
prev: {
|
|
1107
|
-
line: Number.parseInt(prevLine, 10),
|
|
1108
|
-
count: Number.parseInt(prevAdded, 10)
|
|
1109
|
-
},
|
|
1110
|
-
curr: {
|
|
1111
|
-
line: Number.parseInt(currLine, 10),
|
|
1112
|
-
count: Number.parseInt(currAdded, 10)
|
|
1113
|
-
}
|
|
1114
|
-
};
|
|
1115
|
-
});
|
|
1116
|
-
}
|
|
1117
|
-
function isFileChanged(changedFiles, file) {
|
|
1118
|
-
return file in changedFiles;
|
|
1119
|
-
}
|
|
1120
|
-
function adjustFileName(changedFiles, file) {
|
|
1121
|
-
return Object.entries(changedFiles).find(
|
|
1122
|
-
([, { originalFile }]) => originalFile === file
|
|
1123
|
-
)?.[0] ?? file;
|
|
1124
|
-
}
|
|
1125
|
-
function adjustLine(changedFiles, file, line) {
|
|
1126
|
-
const changedFile = changedFiles[adjustFileName(changedFiles, file)];
|
|
1127
|
-
if (!changedFile) {
|
|
1128
|
-
return line;
|
|
1129
|
-
}
|
|
1130
|
-
const offset = changedFile.lineChanges.filter(({ prev }) => prev.line < line).reduce((acc, { prev, curr }) => acc + (curr.count - prev.count), 0);
|
|
1131
|
-
return line + offset;
|
|
1132
|
-
}
|
|
1133
|
-
|
|
1134
|
-
// packages/ci/src/lib/issues.ts
|
|
1135
|
-
function filterRelevantIssues({
|
|
1136
|
-
currReport,
|
|
1137
|
-
prevReport,
|
|
1138
|
-
reportsDiff,
|
|
1139
|
-
changedFiles
|
|
1140
|
-
}) {
|
|
1141
|
-
const auditsWithPlugin = [
|
|
1142
|
-
...reportsDiff.audits.changed,
|
|
1143
|
-
...reportsDiff.audits.added
|
|
1144
|
-
].map((auditLink) => {
|
|
1145
|
-
const plugin = currReport.plugins.find(
|
|
1146
|
-
({ slug }) => slug === auditLink.plugin.slug
|
|
1147
|
-
);
|
|
1148
|
-
const audit = plugin?.audits.find(({ slug }) => slug === auditLink.slug);
|
|
1149
|
-
return plugin && audit && [plugin, audit];
|
|
1150
|
-
}).filter((ctx) => ctx != null);
|
|
1151
|
-
const issues = auditsWithPlugin.flatMap(
|
|
1152
|
-
([plugin, audit]) => getAuditIssues(audit, plugin)
|
|
1153
|
-
);
|
|
1154
|
-
const prevIssues = prevReport.plugins.flatMap(
|
|
1155
|
-
(plugin) => plugin.audits.flatMap((audit) => getAuditIssues(audit, plugin))
|
|
1156
|
-
);
|
|
1157
|
-
return issues.filter(
|
|
1158
|
-
(issue) => isFileChanged(changedFiles, issue.source.file) && !prevIssues.some(
|
|
1159
|
-
(prevIssue) => issuesMatch(prevIssue, issue, changedFiles)
|
|
1160
|
-
)
|
|
1161
|
-
).sort(createIssuesSortCompareFn(currReport));
|
|
1162
|
-
}
|
|
1163
|
-
function getAuditIssues(audit, plugin) {
|
|
1164
|
-
return audit.details?.issues?.filter((issue) => issue.source?.file != null).map((issue) => ({ ...issue, audit, plugin })) ?? [];
|
|
1165
|
-
}
|
|
1166
|
-
function issuesMatch(prev, curr, changedFiles) {
|
|
1167
|
-
return prev.plugin.slug === curr.plugin.slug && prev.audit.slug === curr.audit.slug && prev.severity === curr.severity && removeDigits(prev.message) === removeDigits(curr.message) && adjustFileName(changedFiles, prev.source.file) === curr.source.file && positionsMatch(prev.source, curr.source, changedFiles);
|
|
1168
|
-
}
|
|
1169
|
-
function removeDigits(message) {
|
|
1170
|
-
return message.replace(/\d+/g, "");
|
|
1171
|
-
}
|
|
1172
|
-
function positionsMatch(prev, curr, changedFiles) {
|
|
1173
|
-
if (!hasPosition(prev) || !hasPosition(curr)) {
|
|
1174
|
-
return hasPosition(prev) === hasPosition(curr);
|
|
1175
|
-
}
|
|
1176
|
-
return adjustedStartLinesMatch(prev, curr, changedFiles) || adjustedLineSpansMatch(prev, curr, changedFiles);
|
|
1177
|
-
}
|
|
1178
|
-
function hasPosition(source) {
|
|
1179
|
-
return source.position != null;
|
|
1180
|
-
}
|
|
1181
|
-
function adjustedStartLinesMatch(prev, curr, changedFiles) {
|
|
1182
|
-
return adjustLine(changedFiles, prev.file, prev.position.startLine) === curr.position.startLine;
|
|
1183
|
-
}
|
|
1184
|
-
function adjustedLineSpansMatch(prev, curr, changedFiles) {
|
|
1185
|
-
if (prev.position?.endLine == null || curr.position?.endLine == null) {
|
|
1186
|
-
return false;
|
|
1187
|
-
}
|
|
1188
|
-
const prevLineCount = prev.position.endLine - prev.position.startLine;
|
|
1189
|
-
const currLineCount = curr.position.endLine - curr.position.startLine;
|
|
1190
|
-
const currStartLineOffset = adjustLine(changedFiles, curr.file, curr.position.startLine) - curr.position.startLine;
|
|
1191
|
-
return prevLineCount === currLineCount - currStartLineOffset;
|
|
1192
|
-
}
|
|
1193
|
-
function createIssuesSortCompareFn(report) {
|
|
1194
|
-
return (a, b) => getAuditImpactValue(b, report) - getAuditImpactValue(a, report);
|
|
1195
|
-
}
|
|
1196
|
-
function getAuditImpactValue({ audit, plugin }, report) {
|
|
1197
|
-
return report.categories.map((category) => {
|
|
1198
|
-
const weights = category.refs.map((ref) => {
|
|
1199
|
-
if (ref.plugin !== plugin.slug) {
|
|
1200
|
-
return 0;
|
|
1201
|
-
}
|
|
1202
|
-
switch (ref.type) {
|
|
1203
|
-
case "audit":
|
|
1204
|
-
return ref.slug === audit.slug ? ref.weight : 0;
|
|
1205
|
-
case "group":
|
|
1206
|
-
const group = report.plugins.find(({ slug }) => slug === ref.plugin)?.groups?.find(({ slug }) => slug === ref.slug);
|
|
1207
|
-
if (!group?.refs.length) {
|
|
1208
|
-
return 0;
|
|
1209
|
-
}
|
|
1210
|
-
const groupRatio = (group.refs.find(({ slug }) => slug === audit.slug)?.weight ?? 0) / group.refs.reduce((acc, { weight }) => acc + weight, 0);
|
|
1211
|
-
return ref.weight * groupRatio;
|
|
1212
|
-
}
|
|
1213
|
-
});
|
|
1214
|
-
return weights.reduce((acc, weight) => acc + weight, 0) / category.refs.reduce((acc, { weight }) => acc + weight, 0);
|
|
1215
|
-
}).reduce((acc, value) => acc + value, 0);
|
|
1216
|
-
}
|
|
1217
|
-
|
|
1218
|
-
// packages/ci/src/lib/monorepo/list-projects.ts
|
|
1219
|
-
import { glob as glob2 } from "glob";
|
|
1220
|
-
import { join as join7 } from "node:path";
|
|
1221
|
-
|
|
1222
|
-
// packages/ci/src/lib/monorepo/handlers/npm.ts
|
|
1223
|
-
import { join as join2 } from "node:path";
|
|
1224
|
-
|
|
1225
|
-
// packages/ci/src/lib/monorepo/packages.ts
|
|
1226
|
-
import { glob } from "glob";
|
|
1227
|
-
import { basename, dirname, join } from "node:path";
|
|
1228
|
-
async function listPackages(cwd, patterns = ["**"]) {
|
|
1229
|
-
const files = await glob(
|
|
1230
|
-
patterns.map((pattern) => pattern.replace(/\/?$/, "/package.json")),
|
|
1231
|
-
{ cwd }
|
|
1232
|
-
);
|
|
1233
|
-
return Promise.all(
|
|
1234
|
-
files.toSorted().map(async (file) => {
|
|
1235
|
-
const packageJson = await readJsonFile(join(cwd, file));
|
|
1236
|
-
const directory = join(cwd, dirname(file));
|
|
1237
|
-
const name = packageJson.name || basename(directory);
|
|
1238
|
-
return { name, directory, packageJson };
|
|
1239
|
-
})
|
|
1240
|
-
);
|
|
1241
|
-
}
|
|
1242
|
-
async function listWorkspaces(cwd) {
|
|
1243
|
-
const rootPackageJson = await readRootPackageJson(cwd);
|
|
1244
|
-
const patterns = Array.isArray(rootPackageJson.workspaces) ? rootPackageJson.workspaces : rootPackageJson.workspaces?.packages;
|
|
1245
|
-
return {
|
|
1246
|
-
workspaces: await listPackages(cwd, patterns),
|
|
1247
|
-
rootPackageJson
|
|
1248
|
-
};
|
|
1249
|
-
}
|
|
1250
|
-
async function hasWorkspacesEnabled(cwd) {
|
|
1251
|
-
const packageJson = await readRootPackageJson(cwd);
|
|
1252
|
-
if (!packageJson.private) {
|
|
1253
|
-
return false;
|
|
1254
|
-
}
|
|
1255
|
-
if (Array.isArray(packageJson.workspaces)) {
|
|
1256
|
-
return packageJson.workspaces.length > 0;
|
|
1257
|
-
}
|
|
1258
|
-
if (typeof packageJson.workspaces === "object") {
|
|
1259
|
-
return Boolean(packageJson.workspaces.packages?.length);
|
|
1260
|
-
}
|
|
1261
|
-
return false;
|
|
1262
|
-
}
|
|
1263
|
-
async function readRootPackageJson(cwd) {
|
|
1264
|
-
return await readJsonFile(join(cwd, "package.json"));
|
|
1265
|
-
}
|
|
1266
|
-
function hasDependency(packageJson, name) {
|
|
1267
|
-
const { dependencies = {}, devDependencies = {} } = packageJson;
|
|
1268
|
-
return name in devDependencies || name in dependencies;
|
|
1269
|
-
}
|
|
1270
|
-
function hasScript(packageJson, script) {
|
|
1271
|
-
const { scripts = {} } = packageJson;
|
|
1272
|
-
return script in scripts;
|
|
1273
|
-
}
|
|
1274
|
-
function hasCodePushUpDependency(packageJson) {
|
|
1275
|
-
return hasDependency(packageJson, "@code-pushup/cli");
|
|
1276
|
-
}
|
|
1277
|
-
|
|
1278
|
-
// packages/ci/src/lib/monorepo/handlers/npm.ts
|
|
1279
|
-
var npmHandler = {
|
|
1280
|
-
tool: "npm",
|
|
1281
|
-
async isConfigured(options) {
|
|
1282
|
-
return await fileExists(join2(options.cwd, "package-lock.json")) && await hasWorkspacesEnabled(options.cwd);
|
|
1283
|
-
},
|
|
1284
|
-
async listProjects(options) {
|
|
1285
|
-
const { workspaces, rootPackageJson } = await listWorkspaces(options.cwd);
|
|
1286
|
-
return workspaces.filter(
|
|
1287
|
-
({ packageJson }) => hasScript(packageJson, options.task) || hasCodePushUpDependency(packageJson) || hasCodePushUpDependency(rootPackageJson)
|
|
1288
|
-
).map(({ name, packageJson }) => ({
|
|
1289
|
-
name,
|
|
1290
|
-
bin: hasScript(packageJson, options.task) ? `npm -w ${name} run ${options.task} --` : `npm -w ${name} exec ${options.task} --`
|
|
1291
|
-
}));
|
|
1292
|
-
}
|
|
909
|
+
// packages/ci/src/lib/monorepo/handlers/npm.ts
|
|
910
|
+
var npmHandler = {
|
|
911
|
+
tool: "npm",
|
|
912
|
+
async isConfigured(options) {
|
|
913
|
+
return await fileExists(join2(options.cwd, "package-lock.json")) && await hasWorkspacesEnabled(options.cwd);
|
|
914
|
+
},
|
|
915
|
+
async listProjects(options) {
|
|
916
|
+
const { workspaces, rootPackageJson } = await listWorkspaces(options.cwd);
|
|
917
|
+
return workspaces.filter(
|
|
918
|
+
({ packageJson }) => hasScript(packageJson, options.task) || hasCodePushUpDependency(packageJson) || hasCodePushUpDependency(rootPackageJson)
|
|
919
|
+
).map(({ name, packageJson }) => ({
|
|
920
|
+
name,
|
|
921
|
+
bin: hasScript(packageJson, options.task) ? `npm -w ${name} run ${options.task} --` : `npm -w ${name} exec ${options.task} --`
|
|
922
|
+
}));
|
|
923
|
+
}
|
|
1293
924
|
};
|
|
1294
925
|
|
|
1295
926
|
// packages/ci/src/lib/monorepo/handlers/nx.ts
|
|
@@ -1525,6 +1156,381 @@ async function listProjectsByNpmPackages(args) {
|
|
|
1525
1156
|
}));
|
|
1526
1157
|
}
|
|
1527
1158
|
|
|
1159
|
+
// packages/ci/src/lib/monorepo/tools.ts
|
|
1160
|
+
var MONOREPO_TOOLS = ["nx", "turbo", "yarn", "pnpm", "npm"];
|
|
1161
|
+
function isMonorepoTool(value) {
|
|
1162
|
+
return MONOREPO_TOOLS.includes(value);
|
|
1163
|
+
}
|
|
1164
|
+
|
|
1165
|
+
// packages/ci/src/lib/run.ts
|
|
1166
|
+
import fs from "node:fs/promises";
|
|
1167
|
+
import path2 from "node:path";
|
|
1168
|
+
import { simpleGit as simpleGit4 } from "simple-git";
|
|
1169
|
+
|
|
1170
|
+
// packages/ci/src/lib/cli/persist.ts
|
|
1171
|
+
import path from "node:path";
|
|
1172
|
+
function persistCliOptions({
|
|
1173
|
+
directory,
|
|
1174
|
+
project
|
|
1175
|
+
}) {
|
|
1176
|
+
return [
|
|
1177
|
+
`--persist.outputDir=${path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR)}`,
|
|
1178
|
+
`--persist.filename=${createFilename(project)}`,
|
|
1179
|
+
...DEFAULT_PERSIST_FORMAT.map((format) => `--persist.format=${format}`)
|
|
1180
|
+
];
|
|
1181
|
+
}
|
|
1182
|
+
function persistedCliFiles({
|
|
1183
|
+
directory,
|
|
1184
|
+
isDiff,
|
|
1185
|
+
project,
|
|
1186
|
+
formats
|
|
1187
|
+
}) {
|
|
1188
|
+
const rootDir = path.join(directory, DEFAULT_PERSIST_OUTPUT_DIR);
|
|
1189
|
+
const filename = isDiff ? `${createFilename(project)}-diff` : createFilename(project);
|
|
1190
|
+
const filePaths = (formats ?? DEFAULT_PERSIST_FORMAT).reduce(
|
|
1191
|
+
(acc, format) => ({
|
|
1192
|
+
...acc,
|
|
1193
|
+
[`${format}FilePath`]: path.join(rootDir, `${filename}.${format}`)
|
|
1194
|
+
}),
|
|
1195
|
+
// eslint-disable-next-line @typescript-eslint/prefer-reduce-type-parameter, @typescript-eslint/consistent-type-assertions
|
|
1196
|
+
{}
|
|
1197
|
+
);
|
|
1198
|
+
const files = Object.values(filePaths);
|
|
1199
|
+
return {
|
|
1200
|
+
...filePaths,
|
|
1201
|
+
artifactData: {
|
|
1202
|
+
rootDir,
|
|
1203
|
+
files
|
|
1204
|
+
}
|
|
1205
|
+
};
|
|
1206
|
+
}
|
|
1207
|
+
function createFilename(project) {
|
|
1208
|
+
if (!project) {
|
|
1209
|
+
return DEFAULT_PERSIST_FILENAME;
|
|
1210
|
+
}
|
|
1211
|
+
const prefix = projectToFilename(project);
|
|
1212
|
+
return `${prefix}-${DEFAULT_PERSIST_FILENAME}`;
|
|
1213
|
+
}
|
|
1214
|
+
|
|
1215
|
+
// packages/ci/src/lib/cli/commands/collect.ts
|
|
1216
|
+
async function runCollect({
|
|
1217
|
+
bin,
|
|
1218
|
+
config,
|
|
1219
|
+
directory,
|
|
1220
|
+
silent,
|
|
1221
|
+
project
|
|
1222
|
+
}) {
|
|
1223
|
+
const { stdout } = await executeProcess({
|
|
1224
|
+
command: bin,
|
|
1225
|
+
args: [
|
|
1226
|
+
...config ? [`--config=${config}`] : [],
|
|
1227
|
+
...persistCliOptions({ directory, project })
|
|
1228
|
+
],
|
|
1229
|
+
cwd: directory
|
|
1230
|
+
});
|
|
1231
|
+
if (!silent) {
|
|
1232
|
+
console.info(stdout);
|
|
1233
|
+
}
|
|
1234
|
+
return persistedCliFiles({ directory, project });
|
|
1235
|
+
}
|
|
1236
|
+
|
|
1237
|
+
// packages/ci/src/lib/cli/commands/compare.ts
|
|
1238
|
+
async function runCompare({ before, after, label }, { bin, config, directory, silent, project }) {
|
|
1239
|
+
const { stdout } = await executeProcess({
|
|
1240
|
+
command: bin,
|
|
1241
|
+
args: [
|
|
1242
|
+
"compare",
|
|
1243
|
+
`--before=${before}`,
|
|
1244
|
+
`--after=${after}`,
|
|
1245
|
+
...label ? [`--label=${label}`] : [],
|
|
1246
|
+
...config ? [`--config=${config}`] : [],
|
|
1247
|
+
...persistCliOptions({ directory, project })
|
|
1248
|
+
],
|
|
1249
|
+
cwd: directory
|
|
1250
|
+
});
|
|
1251
|
+
if (!silent) {
|
|
1252
|
+
console.info(stdout);
|
|
1253
|
+
}
|
|
1254
|
+
return persistedCliFiles({ directory, isDiff: true, project });
|
|
1255
|
+
}
|
|
1256
|
+
|
|
1257
|
+
// packages/ci/src/lib/cli/commands/merge-diffs.ts
|
|
1258
|
+
async function runMergeDiffs(files, { bin, config, directory, silent }) {
|
|
1259
|
+
const { stdout } = await executeProcess({
|
|
1260
|
+
command: bin,
|
|
1261
|
+
args: [
|
|
1262
|
+
"merge-diffs",
|
|
1263
|
+
...files.map((file) => `--files=${file}`),
|
|
1264
|
+
...config ? [`--config=${config}`] : [],
|
|
1265
|
+
...persistCliOptions({ directory })
|
|
1266
|
+
],
|
|
1267
|
+
cwd: directory
|
|
1268
|
+
});
|
|
1269
|
+
if (!silent) {
|
|
1270
|
+
console.info(stdout);
|
|
1271
|
+
}
|
|
1272
|
+
return persistedCliFiles({ directory, isDiff: true, formats: ["md"] });
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
// packages/ci/src/lib/cli/commands/print-config.ts
|
|
1276
|
+
async function runPrintConfig({
|
|
1277
|
+
bin,
|
|
1278
|
+
config,
|
|
1279
|
+
directory,
|
|
1280
|
+
silent
|
|
1281
|
+
}) {
|
|
1282
|
+
const { stdout } = await executeProcess({
|
|
1283
|
+
command: bin,
|
|
1284
|
+
args: [...config ? [`--config=${config}`] : [], "print-config"],
|
|
1285
|
+
cwd: directory
|
|
1286
|
+
});
|
|
1287
|
+
if (!silent) {
|
|
1288
|
+
console.info(stdout);
|
|
1289
|
+
}
|
|
1290
|
+
}
|
|
1291
|
+
|
|
1292
|
+
// packages/ci/src/lib/cli/context.ts
|
|
1293
|
+
function createCommandContext(settings, project) {
|
|
1294
|
+
return {
|
|
1295
|
+
project: project?.name,
|
|
1296
|
+
bin: project?.bin ?? settings.bin,
|
|
1297
|
+
directory: project?.directory ?? settings.directory,
|
|
1298
|
+
config: settings.config,
|
|
1299
|
+
silent: settings.silent
|
|
1300
|
+
};
|
|
1301
|
+
}
|
|
1302
|
+
|
|
1303
|
+
// packages/ci/src/lib/comment.ts
|
|
1304
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
1305
|
+
async function commentOnPR(mdPath, api, logger) {
|
|
1306
|
+
const markdown = await readFile2(mdPath, "utf8");
|
|
1307
|
+
const identifier = `<!-- generated by @code-pushup/ci -->`;
|
|
1308
|
+
const body = truncateBody(
|
|
1309
|
+
`${markdown}
|
|
1310
|
+
|
|
1311
|
+
${identifier}
|
|
1312
|
+
`,
|
|
1313
|
+
api.maxCommentChars,
|
|
1314
|
+
logger
|
|
1315
|
+
);
|
|
1316
|
+
const comments = await api.listComments();
|
|
1317
|
+
logger.debug(`Fetched ${comments.length} comments for pull request`);
|
|
1318
|
+
const prevComment = comments.find(
|
|
1319
|
+
(comment) => comment.body.includes(identifier)
|
|
1320
|
+
);
|
|
1321
|
+
logger.debug(
|
|
1322
|
+
prevComment ? `Found previous comment ${prevComment.id} from Code PushUp` : "Previous Code PushUp comment not found"
|
|
1323
|
+
);
|
|
1324
|
+
if (prevComment) {
|
|
1325
|
+
const updatedComment = await api.updateComment(prevComment.id, body);
|
|
1326
|
+
logger.info(`Updated body of comment ${updatedComment.url}`);
|
|
1327
|
+
return updatedComment.id;
|
|
1328
|
+
}
|
|
1329
|
+
const createdComment = await api.createComment(body);
|
|
1330
|
+
logger.info(`Created new comment ${createdComment.url}`);
|
|
1331
|
+
return createdComment.id;
|
|
1332
|
+
}
|
|
1333
|
+
function truncateBody(body, max, logger) {
|
|
1334
|
+
const truncateWarning = "...*[Comment body truncated]*";
|
|
1335
|
+
if (body.length > max) {
|
|
1336
|
+
logger.warn(`Comment body is too long. Truncating to ${max} characters.`);
|
|
1337
|
+
return body.slice(0, max - truncateWarning.length) + truncateWarning;
|
|
1338
|
+
}
|
|
1339
|
+
return body;
|
|
1340
|
+
}
|
|
1341
|
+
|
|
1342
|
+
// packages/ci/src/lib/constants.ts
|
|
1343
|
+
var DEFAULT_SETTINGS = {
|
|
1344
|
+
monorepo: false,
|
|
1345
|
+
projects: null,
|
|
1346
|
+
task: "code-pushup",
|
|
1347
|
+
bin: "npx --no-install code-pushup",
|
|
1348
|
+
config: null,
|
|
1349
|
+
directory: process.cwd(),
|
|
1350
|
+
silent: false,
|
|
1351
|
+
debug: false,
|
|
1352
|
+
detectNewIssues: true,
|
|
1353
|
+
logger: console
|
|
1354
|
+
};
|
|
1355
|
+
|
|
1356
|
+
// packages/ci/src/lib/git.ts
|
|
1357
|
+
import { DiffNameStatus, simpleGit as simpleGit3 } from "simple-git";
|
|
1358
|
+
async function listChangedFiles(refs, git = simpleGit3()) {
|
|
1359
|
+
const statuses = [
|
|
1360
|
+
DiffNameStatus.ADDED,
|
|
1361
|
+
DiffNameStatus.COPIED,
|
|
1362
|
+
DiffNameStatus.MODIFIED,
|
|
1363
|
+
DiffNameStatus.RENAMED
|
|
1364
|
+
];
|
|
1365
|
+
const { files } = await git.diffSummary([
|
|
1366
|
+
refs.base,
|
|
1367
|
+
refs.head,
|
|
1368
|
+
`--diff-filter=${statuses.join("")}`,
|
|
1369
|
+
"--find-renames",
|
|
1370
|
+
"--find-copies"
|
|
1371
|
+
]);
|
|
1372
|
+
const entries = await Promise.all(
|
|
1373
|
+
files.filter(({ binary }) => !binary).map(({ file }) => {
|
|
1374
|
+
const rename = parseFileRename(file);
|
|
1375
|
+
if (rename) {
|
|
1376
|
+
return { file: rename.curr, originalFile: rename.prev };
|
|
1377
|
+
}
|
|
1378
|
+
return { file };
|
|
1379
|
+
}).map(async ({ file, originalFile }) => {
|
|
1380
|
+
const diff = await git.diff([
|
|
1381
|
+
"--unified=0",
|
|
1382
|
+
refs.base,
|
|
1383
|
+
refs.head,
|
|
1384
|
+
"--",
|
|
1385
|
+
file,
|
|
1386
|
+
...originalFile ? [originalFile] : []
|
|
1387
|
+
]);
|
|
1388
|
+
const lineChanges = parseDiff(diff);
|
|
1389
|
+
return [
|
|
1390
|
+
file,
|
|
1391
|
+
{ ...originalFile && { originalFile }, lineChanges }
|
|
1392
|
+
];
|
|
1393
|
+
})
|
|
1394
|
+
);
|
|
1395
|
+
return Object.fromEntries(entries);
|
|
1396
|
+
}
|
|
1397
|
+
function parseFileRename(file) {
|
|
1398
|
+
const partialRenameMatch = file.match(/^(.*){(.*) => (.*)}(.*)$/);
|
|
1399
|
+
if (partialRenameMatch) {
|
|
1400
|
+
const [, prefix = "", prev, curr, suffix] = partialRenameMatch;
|
|
1401
|
+
return {
|
|
1402
|
+
prev: prefix + prev + suffix,
|
|
1403
|
+
curr: prefix + curr + suffix
|
|
1404
|
+
};
|
|
1405
|
+
}
|
|
1406
|
+
const fullRenameMatch = file.match(/^(.*) => (.*)$/);
|
|
1407
|
+
if (fullRenameMatch) {
|
|
1408
|
+
const [, prev = "", curr = ""] = fullRenameMatch;
|
|
1409
|
+
return { prev, curr };
|
|
1410
|
+
}
|
|
1411
|
+
return null;
|
|
1412
|
+
}
|
|
1413
|
+
function parseDiff(diff) {
|
|
1414
|
+
const changeSummaries = diff.match(/@@ [ \d,+-]+ @@/g);
|
|
1415
|
+
if (changeSummaries == null) {
|
|
1416
|
+
return [];
|
|
1417
|
+
}
|
|
1418
|
+
return changeSummaries.map((summary) => summary.match(/^@@ -(\d+|\d+,\d+) \+(\d+|\d+,\d+) @@$/)).filter((matches) => matches != null).map((matches) => {
|
|
1419
|
+
const [prevLine = "", prevAdded = "1"] = matches[1].split(",");
|
|
1420
|
+
const [currLine = "", currAdded = "1"] = matches[2].split(",");
|
|
1421
|
+
return {
|
|
1422
|
+
prev: {
|
|
1423
|
+
line: Number.parseInt(prevLine, 10),
|
|
1424
|
+
count: Number.parseInt(prevAdded, 10)
|
|
1425
|
+
},
|
|
1426
|
+
curr: {
|
|
1427
|
+
line: Number.parseInt(currLine, 10),
|
|
1428
|
+
count: Number.parseInt(currAdded, 10)
|
|
1429
|
+
}
|
|
1430
|
+
};
|
|
1431
|
+
});
|
|
1432
|
+
}
|
|
1433
|
+
function isFileChanged(changedFiles, file) {
|
|
1434
|
+
return file in changedFiles;
|
|
1435
|
+
}
|
|
1436
|
+
function adjustFileName(changedFiles, file) {
|
|
1437
|
+
return Object.entries(changedFiles).find(
|
|
1438
|
+
([, { originalFile }]) => originalFile === file
|
|
1439
|
+
)?.[0] ?? file;
|
|
1440
|
+
}
|
|
1441
|
+
function adjustLine(changedFiles, file, line) {
|
|
1442
|
+
const changedFile = changedFiles[adjustFileName(changedFiles, file)];
|
|
1443
|
+
if (!changedFile) {
|
|
1444
|
+
return line;
|
|
1445
|
+
}
|
|
1446
|
+
const offset = changedFile.lineChanges.filter(({ prev }) => prev.line < line).reduce((acc, { prev, curr }) => acc + (curr.count - prev.count), 0);
|
|
1447
|
+
return line + offset;
|
|
1448
|
+
}
|
|
1449
|
+
|
|
1450
|
+
// packages/ci/src/lib/issues.ts
|
|
1451
|
+
function filterRelevantIssues({
|
|
1452
|
+
currReport,
|
|
1453
|
+
prevReport,
|
|
1454
|
+
reportsDiff,
|
|
1455
|
+
changedFiles
|
|
1456
|
+
}) {
|
|
1457
|
+
const auditsWithPlugin = [
|
|
1458
|
+
...reportsDiff.audits.changed,
|
|
1459
|
+
...reportsDiff.audits.added
|
|
1460
|
+
].map((auditLink) => {
|
|
1461
|
+
const plugin = currReport.plugins.find(
|
|
1462
|
+
({ slug }) => slug === auditLink.plugin.slug
|
|
1463
|
+
);
|
|
1464
|
+
const audit = plugin?.audits.find(({ slug }) => slug === auditLink.slug);
|
|
1465
|
+
return plugin && audit && [plugin, audit];
|
|
1466
|
+
}).filter((ctx) => ctx != null);
|
|
1467
|
+
const issues = auditsWithPlugin.flatMap(
|
|
1468
|
+
([plugin, audit]) => getAuditIssues(audit, plugin)
|
|
1469
|
+
);
|
|
1470
|
+
const prevIssues = prevReport.plugins.flatMap(
|
|
1471
|
+
(plugin) => plugin.audits.flatMap((audit) => getAuditIssues(audit, plugin))
|
|
1472
|
+
);
|
|
1473
|
+
return issues.filter(
|
|
1474
|
+
(issue) => isFileChanged(changedFiles, issue.source.file) && !prevIssues.some(
|
|
1475
|
+
(prevIssue) => issuesMatch(prevIssue, issue, changedFiles)
|
|
1476
|
+
)
|
|
1477
|
+
).sort(createIssuesSortCompareFn(currReport));
|
|
1478
|
+
}
|
|
1479
|
+
function getAuditIssues(audit, plugin) {
|
|
1480
|
+
return audit.details?.issues?.filter((issue) => issue.source?.file != null).map((issue) => ({ ...issue, audit, plugin })) ?? [];
|
|
1481
|
+
}
|
|
1482
|
+
function issuesMatch(prev, curr, changedFiles) {
|
|
1483
|
+
return prev.plugin.slug === curr.plugin.slug && prev.audit.slug === curr.audit.slug && prev.severity === curr.severity && removeDigits(prev.message) === removeDigits(curr.message) && adjustFileName(changedFiles, prev.source.file) === curr.source.file && positionsMatch(prev.source, curr.source, changedFiles);
|
|
1484
|
+
}
|
|
1485
|
+
function removeDigits(message) {
|
|
1486
|
+
return message.replace(/\d+/g, "");
|
|
1487
|
+
}
|
|
1488
|
+
function positionsMatch(prev, curr, changedFiles) {
|
|
1489
|
+
if (!hasPosition(prev) || !hasPosition(curr)) {
|
|
1490
|
+
return hasPosition(prev) === hasPosition(curr);
|
|
1491
|
+
}
|
|
1492
|
+
return adjustedStartLinesMatch(prev, curr, changedFiles) || adjustedLineSpansMatch(prev, curr, changedFiles);
|
|
1493
|
+
}
|
|
1494
|
+
function hasPosition(source) {
|
|
1495
|
+
return source.position != null;
|
|
1496
|
+
}
|
|
1497
|
+
function adjustedStartLinesMatch(prev, curr, changedFiles) {
|
|
1498
|
+
return adjustLine(changedFiles, prev.file, prev.position.startLine) === curr.position.startLine;
|
|
1499
|
+
}
|
|
1500
|
+
function adjustedLineSpansMatch(prev, curr, changedFiles) {
|
|
1501
|
+
if (prev.position?.endLine == null || curr.position?.endLine == null) {
|
|
1502
|
+
return false;
|
|
1503
|
+
}
|
|
1504
|
+
const prevLineCount = prev.position.endLine - prev.position.startLine;
|
|
1505
|
+
const currLineCount = curr.position.endLine - curr.position.startLine;
|
|
1506
|
+
const currStartLineOffset = adjustLine(changedFiles, curr.file, curr.position.startLine) - curr.position.startLine;
|
|
1507
|
+
return prevLineCount === currLineCount - currStartLineOffset;
|
|
1508
|
+
}
|
|
1509
|
+
function createIssuesSortCompareFn(report) {
|
|
1510
|
+
return (a, b) => getAuditImpactValue(b, report) - getAuditImpactValue(a, report);
|
|
1511
|
+
}
|
|
1512
|
+
function getAuditImpactValue({ audit, plugin }, report) {
|
|
1513
|
+
return report.categories.map((category) => {
|
|
1514
|
+
const weights = category.refs.map((ref) => {
|
|
1515
|
+
if (ref.plugin !== plugin.slug) {
|
|
1516
|
+
return 0;
|
|
1517
|
+
}
|
|
1518
|
+
switch (ref.type) {
|
|
1519
|
+
case "audit":
|
|
1520
|
+
return ref.slug === audit.slug ? ref.weight : 0;
|
|
1521
|
+
case "group":
|
|
1522
|
+
const group = report.plugins.find(({ slug }) => slug === ref.plugin)?.groups?.find(({ slug }) => slug === ref.slug);
|
|
1523
|
+
if (!group?.refs.length) {
|
|
1524
|
+
return 0;
|
|
1525
|
+
}
|
|
1526
|
+
const groupRatio = (group.refs.find(({ slug }) => slug === audit.slug)?.weight ?? 0) / group.refs.reduce((acc, { weight }) => acc + weight, 0);
|
|
1527
|
+
return ref.weight * groupRatio;
|
|
1528
|
+
}
|
|
1529
|
+
});
|
|
1530
|
+
return weights.reduce((acc, weight) => acc + weight, 0) / category.refs.reduce((acc, { weight }) => acc + weight, 0);
|
|
1531
|
+
}).reduce((acc, value) => acc + value, 0);
|
|
1532
|
+
}
|
|
1533
|
+
|
|
1528
1534
|
// packages/ci/src/lib/run.ts
|
|
1529
1535
|
async function runInCI(refs, api, options, git = simpleGit4()) {
|
|
1530
1536
|
const settings = { ...DEFAULT_SETTINGS, ...options };
|
|
@@ -1704,5 +1710,7 @@ async function findNewIssues(args) {
|
|
|
1704
1710
|
return issues;
|
|
1705
1711
|
}
|
|
1706
1712
|
export {
|
|
1713
|
+
MONOREPO_TOOLS,
|
|
1714
|
+
isMonorepoTool,
|
|
1707
1715
|
runInCI
|
|
1708
1716
|
};
|
package/package.json
CHANGED
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@code-pushup/ci",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.53.0",
|
|
4
4
|
"description": "CI automation logic for Code PushUp (provider-agnostic)",
|
|
5
5
|
"dependencies": {
|
|
6
|
-
"@code-pushup/models": "0.
|
|
7
|
-
"@code-pushup/utils": "0.
|
|
6
|
+
"@code-pushup/models": "0.53.0",
|
|
7
|
+
"@code-pushup/utils": "0.53.0",
|
|
8
8
|
"glob": "^10.4.5",
|
|
9
9
|
"simple-git": "^3.20.0",
|
|
10
10
|
"yaml": "^2.5.1"
|
package/src/index.d.ts
CHANGED
|
@@ -1,2 +1,2 @@
|
|
|
1
1
|
export { listMonorepoProjects } from './list-projects';
|
|
2
|
-
export { MONOREPO_TOOLS, type MonorepoTool, type ProjectConfig } from './tools';
|
|
2
|
+
export { MONOREPO_TOOLS, isMonorepoTool, type MonorepoTool, type ProjectConfig, } from './tools';
|