apify-test-tools 0.6.3-beta.0 → 0.6.4-beta.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/CHANGELOG.md +0 -9
- package/bin/diff-changes.ts +9 -10
- package/bin/git.ts +45 -5
- package/bin/main.ts +46 -22
- package/dist/bin/diff-changes.d.ts.map +1 -1
- package/dist/bin/diff-changes.js +8 -10
- package/dist/bin/diff-changes.js.map +1 -1
- package/dist/bin/git.d.ts +11 -0
- package/dist/bin/git.d.ts.map +1 -1
- package/dist/bin/git.js +37 -3
- package/dist/bin/git.js.map +1 -1
- package/dist/bin/main.js +33 -22
- package/dist/bin/main.js.map +1 -1
- package/dist/test/unit/bin/git.test.js +68 -1
- package/dist/test/unit/bin/git.test.js.map +1 -1
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +1 -1
- package/test/unit/bin/git.test.ts +87 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,15 +2,6 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
-
<!-- git-cliff-unreleased-start -->
|
|
6
|
-
## 0.6.3 - **not yet released**
|
|
7
|
-
|
|
8
|
-
### 🐛 Bug Fixes
|
|
9
|
-
|
|
10
|
-
- **bin:** Case sensitivity in file path handling for git operations ([#75](https://github.com/apify/apify-test-tools/pull/75)) ([ae15cb6](https://github.com/apify/apify-test-tools/commit/ae15cb6a5e0b52d9332b7149b0b4f760cb41953a)) by [@metalwarrior665](https://github.com/metalwarrior665)
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
<!-- git-cliff-unreleased-end -->
|
|
14
5
|
## [0.6.2](https://github.com/apify/apify-test-tools/releases/tag/v0.6.2) (2026-03-27)
|
|
15
6
|
|
|
16
7
|
### 🚀 Features
|
package/bin/diff-changes.ts
CHANGED
|
@@ -51,9 +51,7 @@ type FileChange =
|
|
|
51
51
|
includes: 'all-actors' | ActorConfig;
|
|
52
52
|
};
|
|
53
53
|
|
|
54
|
-
const classifyFileChange = (
|
|
55
|
-
// Lowercase for case-insensitive matching; keep original for git show (case-sensitive on Linux)
|
|
56
|
-
const lowercaseFilePath = originalFilePath.toLowerCase();
|
|
54
|
+
const classifyFileChange = (lowercaseFilePath: string, actorConfigs: ActorConfig[], commits: Commit[]): FileChange => {
|
|
57
55
|
if (isIgnoredTopLevelFile(lowercaseFilePath)) {
|
|
58
56
|
return { impact: 'ignored' };
|
|
59
57
|
}
|
|
@@ -81,8 +79,7 @@ const classifyFileChange = (originalFilePath: string, actorConfigs: ActorConfig[
|
|
|
81
79
|
if (lowercaseFilePath.endsWith('readme.md')) {
|
|
82
80
|
return { impact: 'cosmetic', includes: actorConfigChanged };
|
|
83
81
|
}
|
|
84
|
-
|
|
85
|
-
if (lowercaseFilePath.endsWith('.json') && isCosmeticOnlyJsonSchemaChange(commits, originalFilePath)) {
|
|
82
|
+
if (lowercaseFilePath.endsWith('.json') && isCosmeticOnlyJsonSchemaChange(commits, lowercaseFilePath)) {
|
|
86
83
|
return { impact: 'cosmetic', includes: actorConfigChanged };
|
|
87
84
|
}
|
|
88
85
|
|
|
@@ -104,8 +101,10 @@ export const getChangedActors = ({
|
|
|
104
101
|
|
|
105
102
|
const actorConfigsWithoutStandalone = actorConfigs.filter(({ isStandalone }) => !isStandalone);
|
|
106
103
|
|
|
107
|
-
|
|
108
|
-
|
|
104
|
+
const lowercaseFiles = filepathsChanged.map((file) => file.toLowerCase());
|
|
105
|
+
|
|
106
|
+
for (const lowercaseFilePath of lowercaseFiles) {
|
|
107
|
+
const fileChange = classifyFileChange(lowercaseFilePath, actorConfigs, commits);
|
|
109
108
|
if (fileChange.impact === 'ignored') {
|
|
110
109
|
continue;
|
|
111
110
|
}
|
|
@@ -127,17 +126,17 @@ export const getChangedActors = ({
|
|
|
127
126
|
const actorsChanged = Array.from(actorsChangedMap.values());
|
|
128
127
|
|
|
129
128
|
// All below here is just for logging
|
|
130
|
-
const ignoredFilesChanged =
|
|
129
|
+
const ignoredFilesChanged = lowercaseFiles.filter(
|
|
131
130
|
(file) => classifyFileChange(file, actorConfigs, commits).impact === 'ignored',
|
|
132
131
|
);
|
|
133
132
|
console.error(`[DIFF]: Ignored files (don't trigger test or build): ${ignoredFilesChanged.join(', ')}`);
|
|
134
133
|
|
|
135
|
-
const cosmeticFilesChanged =
|
|
134
|
+
const cosmeticFilesChanged = lowercaseFiles.filter(
|
|
136
135
|
(file) => classifyFileChange(file, actorConfigs, commits).impact === 'cosmetic',
|
|
137
136
|
);
|
|
138
137
|
console.error(`[DIFF]: Cosmetic files (should only trigger release build): ${cosmeticFilesChanged.join(', ')}`);
|
|
139
138
|
|
|
140
|
-
const functionalFilesChanged =
|
|
139
|
+
const functionalFilesChanged = lowercaseFiles.filter(
|
|
141
140
|
(file) => classifyFileChange(file, actorConfigs, commits).impact === 'functional',
|
|
142
141
|
);
|
|
143
142
|
console.error(`[DIFF]: Functional files (trigger test & release build): ${functionalFilesChanged.join(', ')}`);
|
package/bin/git.ts
CHANGED
|
@@ -17,6 +17,41 @@ export const getChangedFiles = (commits: Commit[]) => {
|
|
|
17
17
|
return changedFiles;
|
|
18
18
|
};
|
|
19
19
|
|
|
20
|
+
/**
|
|
21
|
+
* Returns true if the branch contains a merge commit whose parent is reachable from targetBranch
|
|
22
|
+
* (i.e. a genuine "merge from target" commit, not a merge of some unrelated branch).
|
|
23
|
+
* Uses the full targetBranch..sourceBranch range, ignoring baseCommit.
|
|
24
|
+
*/
|
|
25
|
+
export const hasMergeFromTarget = (sourceBranch: string, targetBranch: string): boolean => {
|
|
26
|
+
const mergeShas = spawnCommandInGhWorkspace(`git log --merges --pretty=format:%H ${targetBranch}..${sourceBranch}`)
|
|
27
|
+
.split('\n')
|
|
28
|
+
.filter(Boolean);
|
|
29
|
+
|
|
30
|
+
for (const sha of mergeShas) {
|
|
31
|
+
const parents = spawnCommandInGhWorkspace(`git log -1 --pretty=format:%P ${sha}`).trim().split(' ');
|
|
32
|
+
for (const parent of parents) {
|
|
33
|
+
// git merge-base A B outputs the common ancestor.
|
|
34
|
+
// If that equals A, then A is an ancestor of B (i.e. parent is reachable from targetBranch).
|
|
35
|
+
const mergeBase = spawnCommandInGhWorkspace(`git merge-base ${parent} ${targetBranch}`);
|
|
36
|
+
if (mergeBase === parent) {
|
|
37
|
+
return true;
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
return false;
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
/**
|
|
45
|
+
* Returns all files touched by non-merge commits on the branch (full history, ignoring baseCommit).
|
|
46
|
+
* Used to check whether the branch itself has any functional changes, independent of what master merged in.
|
|
47
|
+
*/
|
|
48
|
+
export const getBranchOnlyChangedFiles = (sourceBranch: string, targetBranch: string): string[] => {
|
|
49
|
+
const output = spawnCommandInGhWorkspace(
|
|
50
|
+
`git log --no-merges --name-only --pretty=format: ${targetBranch}..${sourceBranch}`,
|
|
51
|
+
);
|
|
52
|
+
return output.split('\n').filter(Boolean);
|
|
53
|
+
};
|
|
54
|
+
|
|
20
55
|
const SHA_REGEX = /^[0-9a-f]{40}$/i;
|
|
21
56
|
|
|
22
57
|
/**
|
|
@@ -40,17 +75,22 @@ export const parseBaseCommit = (shaOrCommit: string | undefined): string | undef
|
|
|
40
75
|
return sha;
|
|
41
76
|
};
|
|
42
77
|
|
|
78
|
+
const fetchAllBranchCommits = (sourceBranch: string, targetBranch: string): Commit[] => {
|
|
79
|
+
const commitsStrings = spawnCommandInGhWorkspace(
|
|
80
|
+
`git log --pretty=format:'${GIT_LOG_FORMAT}' ${targetBranch}..${sourceBranch}`,
|
|
81
|
+
).split('\n');
|
|
82
|
+
const commits = commitsStrings.map((commitString) => parseCommit(commitString));
|
|
83
|
+
commits.reverse();
|
|
84
|
+
return commits;
|
|
85
|
+
};
|
|
86
|
+
|
|
43
87
|
/**
|
|
44
88
|
* Gets the commits between sourceBranch and targetBranch (exclusive).
|
|
45
89
|
* - If baseCommit is provided, only returns commits after the baseCommit.
|
|
46
90
|
*/
|
|
47
91
|
export const getCommits = ({ sourceBranch, targetBranch, baseCommit }: Config): Commit[] => {
|
|
48
92
|
const baseCommitSha = parseBaseCommit(baseCommit);
|
|
49
|
-
const
|
|
50
|
-
`git log --pretty=format:'${GIT_LOG_FORMAT}' ${targetBranch}..${sourceBranch}`,
|
|
51
|
-
).split('\n');
|
|
52
|
-
const commits = commitsStrings.map((commitString) => parseCommit(commitString));
|
|
53
|
-
commits.reverse();
|
|
93
|
+
const commits = fetchAllBranchCommits(sourceBranch, targetBranch);
|
|
54
94
|
|
|
55
95
|
const baseCommitIndex = commits.findIndex((commit) => commit.sha === baseCommitSha);
|
|
56
96
|
|
package/bin/main.ts
CHANGED
|
@@ -8,10 +8,11 @@ import { hideBin } from 'yargs/helpers';
|
|
|
8
8
|
|
|
9
9
|
import { deleteOldBuilds, runBuilds } from './build.js';
|
|
10
10
|
import { getChangedActors } from './diff-changes.js';
|
|
11
|
-
import { getChangedFiles, getCommits } from './git.js';
|
|
11
|
+
import { getBranchOnlyChangedFiles, getChangedFiles, getCommits, hasMergeFromTarget } from './git.js';
|
|
12
12
|
import { getPushData } from './github.js';
|
|
13
13
|
import { notifyToSlack } from './slack.js';
|
|
14
14
|
import { reportTestResults } from './test-report.js';
|
|
15
|
+
import type { Config } from './types.js';
|
|
15
16
|
import { getRepoActors, setCwd, spawnCommandInGhWorkspace } from './utils.js';
|
|
16
17
|
|
|
17
18
|
/**
|
|
@@ -34,6 +35,38 @@ const buildOptions = (y: Argv) => {
|
|
|
34
35
|
});
|
|
35
36
|
};
|
|
36
37
|
|
|
38
|
+
const resolveChangedActors = async (
|
|
39
|
+
{ targetBranch, sourceBranch, baseCommit }: Config,
|
|
40
|
+
{ isLatest }: { isLatest: boolean },
|
|
41
|
+
) => {
|
|
42
|
+
const actorConfigs = await getRepoActors();
|
|
43
|
+
|
|
44
|
+
// This is an optimization for the common case where a branch only has cosmetic changes but had to merge in
|
|
45
|
+
// functional changes from master (being up-to-date is a CI requirement). Master is already validated, and
|
|
46
|
+
// since the branch has no functional changes of its own, there is nothing new to validate.
|
|
47
|
+
// Exception: if the branch has any functional changes alongside the merge, we must re-test — even
|
|
48
|
+
// individually validated changes can have novel interactions when combined.
|
|
49
|
+
if (hasMergeFromTarget(sourceBranch, targetBranch)) {
|
|
50
|
+
const branchOnlyFiles = getBranchOnlyChangedFiles(sourceBranch, targetBranch);
|
|
51
|
+
// Omit baseCommit to get full branch history. Validated functional commits can still interact with merged ones
|
|
52
|
+
const allBranchCommits = getCommits({ sourceBranch, targetBranch, baseCommit: undefined });
|
|
53
|
+
const branchOnlyActorsChanged = getChangedActors({
|
|
54
|
+
filepathsChanged: branchOnlyFiles,
|
|
55
|
+
actorConfigs,
|
|
56
|
+
commits: allBranchCommits,
|
|
57
|
+
});
|
|
58
|
+
if (branchOnlyActorsChanged.length === 0) {
|
|
59
|
+
console.error('[MERGE-FROM-TARGET-OPTIMIZATION]: Branch itself has no functional changes, skipping builds');
|
|
60
|
+
return [];
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// If the optimization doesn't apply, we check all branch commits including merges for full coverage. We don't reuse the merge optimization results because here we can apply baseCommit and check merge commits (they might be functional or just cosmetic)
|
|
65
|
+
const commits = getCommits({ targetBranch, sourceBranch, baseCommit });
|
|
66
|
+
const changedFiles = getChangedFiles(commits);
|
|
67
|
+
return getChangedActors({ filepathsChanged: changedFiles, actorConfigs, isLatest, commits });
|
|
68
|
+
};
|
|
69
|
+
|
|
37
70
|
await yargs()
|
|
38
71
|
.scriptName('public-actors-utils')
|
|
39
72
|
.option('dry-run', {
|
|
@@ -68,16 +101,11 @@ await yargs()
|
|
|
68
101
|
console.log(JSON.stringify(actorConfigs));
|
|
69
102
|
},
|
|
70
103
|
)
|
|
71
|
-
.command('get-affected-actors', '', buildOptions, async (
|
|
72
|
-
const
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
filepathsChanged: changedFiles,
|
|
77
|
-
actorConfigs,
|
|
78
|
-
isLatest: false,
|
|
79
|
-
commits,
|
|
80
|
-
});
|
|
104
|
+
.command('get-affected-actors', '', buildOptions, async ({ targetBranch, sourceBranch, baseCommit }) => {
|
|
105
|
+
const actorsChanged = await resolveChangedActors(
|
|
106
|
+
{ targetBranch, sourceBranch, baseCommit },
|
|
107
|
+
{ isLatest: false },
|
|
108
|
+
);
|
|
81
109
|
console.log(JSON.stringify(actorsChanged));
|
|
82
110
|
})
|
|
83
111
|
.command(
|
|
@@ -97,15 +125,11 @@ await yargs()
|
|
|
97
125
|
'build',
|
|
98
126
|
'',
|
|
99
127
|
(args) => buildOptions(args).option('dry-run', { type: 'boolean', default: false }),
|
|
100
|
-
async (
|
|
101
|
-
const
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
filepathsChanged: changedFiles,
|
|
106
|
-
actorConfigs,
|
|
107
|
-
commits,
|
|
108
|
-
});
|
|
128
|
+
async ({ targetBranch, sourceBranch, baseCommit, dryRun }) => {
|
|
129
|
+
const actorsChanged = await resolveChangedActors(
|
|
130
|
+
{ targetBranch, sourceBranch, baseCommit },
|
|
131
|
+
{ isLatest: false },
|
|
132
|
+
);
|
|
109
133
|
// https://github.com/apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
|
|
110
134
|
// git@github.com:apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
|
|
111
135
|
const repoUrl = spawnCommandInGhWorkspace(`git remote get-url origin`).replace(
|
|
@@ -116,8 +140,8 @@ await yargs()
|
|
|
116
140
|
const builds = await runBuilds({
|
|
117
141
|
repoUrl,
|
|
118
142
|
actorConfigs: actorsChanged,
|
|
119
|
-
branch:
|
|
120
|
-
dryRun
|
|
143
|
+
branch: sourceBranch.replace('origin/', ''),
|
|
144
|
+
dryRun,
|
|
121
145
|
});
|
|
122
146
|
console.log(JSON.stringify(builds));
|
|
123
147
|
},
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-changes.d.ts","sourceRoot":"","sources":["../../bin/diff-changes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,yBAAyB;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,eAAO,MAAM,qBAAqB,GAC9B,mBAAmB,MAAM,KAC1B;IAAE,aAAa,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,KAAK,CAAA;CAOrE,CAAC;
|
|
1
|
+
{"version":3,"file":"diff-changes.d.ts","sourceRoot":"","sources":["../../bin/diff-changes.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEtD,UAAU,yBAAyB;IAC/B,gBAAgB,EAAE,MAAM,EAAE,CAAC;IAC3B,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,OAAO,EAAE,MAAM,EAAE,CAAC;CACrB;AAED,eAAO,MAAM,qBAAqB,GAC9B,mBAAmB,MAAM,KAC1B;IAAE,aAAa,EAAE,IAAI,CAAC;IAAC,SAAS,EAAE,MAAM,CAAA;CAAE,GAAG;IAAE,aAAa,EAAE,KAAK,CAAA;CAOrE,CAAC;AAyEF,eAAO,MAAM,gBAAgB,GAAI,wDAK9B,yBAAyB,KAAG,WAAW,EA0DzC,CAAC"}
|
package/dist/bin/diff-changes.js
CHANGED
|
@@ -27,9 +27,7 @@ const isIgnoredTopLevelFile = (lowercaseFilePath) => {
|
|
|
27
27
|
const sanitizedLowercaseFilePath = lowercaseFilePath.replace(/^code\//, '').replace(/^shared\//, '');
|
|
28
28
|
return IGNORED_TOP_LEVEL_FILES.some((ignoredFile) => sanitizedLowercaseFilePath.startsWith(ignoredFile));
|
|
29
29
|
};
|
|
30
|
-
const classifyFileChange = (
|
|
31
|
-
// Lowercase for case-insensitive matching; keep original for git show (case-sensitive on Linux)
|
|
32
|
-
const lowercaseFilePath = originalFilePath.toLowerCase();
|
|
30
|
+
const classifyFileChange = (lowercaseFilePath, actorConfigs, commits) => {
|
|
33
31
|
if (isIgnoredTopLevelFile(lowercaseFilePath)) {
|
|
34
32
|
return { impact: 'ignored' };
|
|
35
33
|
}
|
|
@@ -50,8 +48,7 @@ const classifyFileChange = (originalFilePath, actorConfigs, commits) => {
|
|
|
50
48
|
if (lowercaseFilePath.endsWith('readme.md')) {
|
|
51
49
|
return { impact: 'cosmetic', includes: actorConfigChanged };
|
|
52
50
|
}
|
|
53
|
-
|
|
54
|
-
if (lowercaseFilePath.endsWith('.json') && isCosmeticOnlyJsonSchemaChange(commits, originalFilePath)) {
|
|
51
|
+
if (lowercaseFilePath.endsWith('.json') && isCosmeticOnlyJsonSchemaChange(commits, lowercaseFilePath)) {
|
|
55
52
|
return { impact: 'cosmetic', includes: actorConfigChanged };
|
|
56
53
|
}
|
|
57
54
|
return { impact: 'functional', includes: actorConfigChanged };
|
|
@@ -63,8 +60,9 @@ export const getChangedActors = ({ filepathsChanged, actorConfigs, isLatest = fa
|
|
|
63
60
|
// folder -> ActorConfig
|
|
64
61
|
const actorsChangedMap = new Map();
|
|
65
62
|
const actorConfigsWithoutStandalone = actorConfigs.filter(({ isStandalone }) => !isStandalone);
|
|
66
|
-
|
|
67
|
-
|
|
63
|
+
const lowercaseFiles = filepathsChanged.map((file) => file.toLowerCase());
|
|
64
|
+
for (const lowercaseFilePath of lowercaseFiles) {
|
|
65
|
+
const fileChange = classifyFileChange(lowercaseFilePath, actorConfigs, commits);
|
|
68
66
|
if (fileChange.impact === 'ignored') {
|
|
69
67
|
continue;
|
|
70
68
|
}
|
|
@@ -83,11 +81,11 @@ export const getChangedActors = ({ filepathsChanged, actorConfigs, isLatest = fa
|
|
|
83
81
|
}
|
|
84
82
|
const actorsChanged = Array.from(actorsChangedMap.values());
|
|
85
83
|
// All below here is just for logging
|
|
86
|
-
const ignoredFilesChanged =
|
|
84
|
+
const ignoredFilesChanged = lowercaseFiles.filter((file) => classifyFileChange(file, actorConfigs, commits).impact === 'ignored');
|
|
87
85
|
console.error(`[DIFF]: Ignored files (don't trigger test or build): ${ignoredFilesChanged.join(', ')}`);
|
|
88
|
-
const cosmeticFilesChanged =
|
|
86
|
+
const cosmeticFilesChanged = lowercaseFiles.filter((file) => classifyFileChange(file, actorConfigs, commits).impact === 'cosmetic');
|
|
89
87
|
console.error(`[DIFF]: Cosmetic files (should only trigger release build): ${cosmeticFilesChanged.join(', ')}`);
|
|
90
|
-
const functionalFilesChanged =
|
|
88
|
+
const functionalFilesChanged = lowercaseFiles.filter((file) => classifyFileChange(file, actorConfigs, commits).impact === 'functional');
|
|
91
89
|
console.error(`[DIFF]: Functional files (trigger test & release build): ${functionalFilesChanged.join(', ')}`);
|
|
92
90
|
if (actorsChanged.length > 0) {
|
|
93
91
|
const miniactors = actorsChanged.filter((config) => !config.isStandalone).map((config) => config.actorName);
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"diff-changes.js","sourceRoot":"","sources":["../../bin/diff-changes.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,8BAA8B,EAAE,MAAM,uBAAuB,CAAC;AAUvE,MAAM,CAAC,MAAM,qBAAqB,GAAG,CACjC,iBAAyB,EAC4C,EAAE;IACvE,MAAM,KAAK,GAAG,iBAAiB,CAAC,KAAK,CAAC,sCAAsC,CAAC,CAAC;IAC9E,IAAI,KAAK,EAAE,CAAC;QACR,gJAAgJ;QAChJ,OAAO,EAAE,aAAa,EAAE,IAAI,EAAE,SAAS,EAAE,KAAK,CAAC,CAAC,CAAC,CAAC,OAAO,CAAC,aAAa,EAAE,GAAG,CAAC,EAAE,CAAC;IACpF,CAAC;IACD,OAAO,EAAE,aAAa,EAAE,KAAK,EAAE,CAAC;AACpC,CAAC,CAAC;AAEF;;GAEG;AACH,MAAM,qBAAqB,GAAG,CAAC,iBAAyB,EAAE,EAAE;IACxD,+HAA+H;IAC/H,MAAM,uBAAuB,GAAG;QAC5B,UAAU;QACV,YAAY;QACZ,WAAW;QACX,SAAS;QACT,WAAW;QACX,mBAAmB;QACnB,aAAa;QACb,eAAe;QACf,SAAS;KACZ,CAAC;IACF,+EAA+E;IAC/E,MAAM,0BAA0B,GAAG,iBAAiB,CAAC,OAAO,CAAC,SAAS,EAAE,EAAE,CAAC,CAAC,OAAO,CAAC,WAAW,EAAE,EAAE,CAAC,CAAC;IAErG,OAAO,uBAAuB,CAAC,IAAI,CAAC,CAAC,WAAW,EAAE,EAAE,CAAC,0BAA0B,CAAC,UAAU,CAAC,WAAW,CAAC,CAAC,CAAC;AAC7G,CAAC,CAAC;AAYF,MAAM,kBAAkB,GAAG,CAAC,
|
|
1
|
+
{"version":3,"file":"diff-changes.js","sourceRoot":"","sources":["../../bin/diff-changes.ts"],"names":[],"mappings":"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"}
|
package/dist/bin/git.d.ts
CHANGED
|
@@ -4,6 +4,17 @@ export declare const GIT_FORMAT_SEPARATOR = "\u00BB\u00A6\u00AB";
|
|
|
4
4
|
* Gets the list of changed files between the given commits (inclusive).
|
|
5
5
|
*/
|
|
6
6
|
export declare const getChangedFiles: (commits: Commit[]) => string[];
|
|
7
|
+
/**
|
|
8
|
+
* Returns true if the branch contains a merge commit whose parent is reachable from targetBranch
|
|
9
|
+
* (i.e. a genuine "merge from target" commit, not a merge of some unrelated branch).
|
|
10
|
+
* Uses the full targetBranch..sourceBranch range, ignoring baseCommit.
|
|
11
|
+
*/
|
|
12
|
+
export declare const hasMergeFromTarget: (sourceBranch: string, targetBranch: string) => boolean;
|
|
13
|
+
/**
|
|
14
|
+
* Returns all files touched by non-merge commits on the branch (full history, ignoring baseCommit).
|
|
15
|
+
* Used to check whether the branch itself has any functional changes, independent of what master merged in.
|
|
16
|
+
*/
|
|
17
|
+
export declare const getBranchOnlyChangedFiles: (sourceBranch: string, targetBranch: string) => string[];
|
|
7
18
|
/**
|
|
8
19
|
*
|
|
9
20
|
* @param shaOrCommit Supports both a SHA string or a Commit object in JSON format. Can be empty.
|
package/dist/bin/git.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../bin/git.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGjD,eAAO,MAAM,oBAAoB,uBAAQ,CAAC;AAG1C;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,aAQhD,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAc1E,CAAC;
|
|
1
|
+
{"version":3,"file":"git.d.ts","sourceRoot":"","sources":["../../bin/git.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAGjD,eAAO,MAAM,oBAAoB,uBAAQ,CAAC;AAG1C;;GAEG;AACH,eAAO,MAAM,eAAe,GAAI,SAAS,MAAM,EAAE,aAQhD,CAAC;AAEF;;;;GAIG;AACH,eAAO,MAAM,kBAAkB,GAAI,cAAc,MAAM,EAAE,cAAc,MAAM,KAAG,OAiB/E,CAAC;AAEF;;;GAGG;AACH,eAAO,MAAM,yBAAyB,GAAI,cAAc,MAAM,EAAE,cAAc,MAAM,KAAG,MAAM,EAK5F,CAAC;AAIF;;;;GAIG;AACH,eAAO,MAAM,eAAe,GAAI,aAAa,MAAM,GAAG,SAAS,KAAG,MAAM,GAAG,SAc1E,CAAC;AAWF;;;GAGG;AACH,eAAO,MAAM,UAAU,GAAI,4CAA4C,MAAM,KAAG,MAAM,EAqBrF,CAAC;AAEF,eAAO,MAAM,WAAW,GAAI,cAAc,MAAM,KAAG,MAYlD,CAAC"}
|
package/dist/bin/git.js
CHANGED
|
@@ -10,6 +10,36 @@ export const getChangedFiles = (commits) => {
|
|
|
10
10
|
console.error(`Changed files (up to 50): ${changedFiles.slice(0, 50).join(', ')}`);
|
|
11
11
|
return changedFiles;
|
|
12
12
|
};
|
|
13
|
+
/**
|
|
14
|
+
* Returns true if the branch contains a merge commit whose parent is reachable from targetBranch
|
|
15
|
+
* (i.e. a genuine "merge from target" commit, not a merge of some unrelated branch).
|
|
16
|
+
* Uses the full targetBranch..sourceBranch range, ignoring baseCommit.
|
|
17
|
+
*/
|
|
18
|
+
export const hasMergeFromTarget = (sourceBranch, targetBranch) => {
|
|
19
|
+
const mergeShas = spawnCommandInGhWorkspace(`git log --merges --pretty=format:%H ${targetBranch}..${sourceBranch}`)
|
|
20
|
+
.split('\n')
|
|
21
|
+
.filter(Boolean);
|
|
22
|
+
for (const sha of mergeShas) {
|
|
23
|
+
const parents = spawnCommandInGhWorkspace(`git log -1 --pretty=format:%P ${sha}`).trim().split(' ');
|
|
24
|
+
for (const parent of parents) {
|
|
25
|
+
// git merge-base A B outputs the common ancestor.
|
|
26
|
+
// If that equals A, then A is an ancestor of B (i.e. parent is reachable from targetBranch).
|
|
27
|
+
const mergeBase = spawnCommandInGhWorkspace(`git merge-base ${parent} ${targetBranch}`);
|
|
28
|
+
if (mergeBase === parent) {
|
|
29
|
+
return true;
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
return false;
|
|
34
|
+
};
|
|
35
|
+
/**
|
|
36
|
+
* Returns all files touched by non-merge commits on the branch (full history, ignoring baseCommit).
|
|
37
|
+
* Used to check whether the branch itself has any functional changes, independent of what master merged in.
|
|
38
|
+
*/
|
|
39
|
+
export const getBranchOnlyChangedFiles = (sourceBranch, targetBranch) => {
|
|
40
|
+
const output = spawnCommandInGhWorkspace(`git log --no-merges --name-only --pretty=format: ${targetBranch}..${sourceBranch}`);
|
|
41
|
+
return output.split('\n').filter(Boolean);
|
|
42
|
+
};
|
|
13
43
|
const SHA_REGEX = /^[0-9a-f]{40}$/i;
|
|
14
44
|
/**
|
|
15
45
|
*
|
|
@@ -31,15 +61,19 @@ export const parseBaseCommit = (shaOrCommit) => {
|
|
|
31
61
|
}
|
|
32
62
|
return sha;
|
|
33
63
|
};
|
|
64
|
+
const fetchAllBranchCommits = (sourceBranch, targetBranch) => {
|
|
65
|
+
const commitsStrings = spawnCommandInGhWorkspace(`git log --pretty=format:'${GIT_LOG_FORMAT}' ${targetBranch}..${sourceBranch}`).split('\n');
|
|
66
|
+
const commits = commitsStrings.map((commitString) => parseCommit(commitString));
|
|
67
|
+
commits.reverse();
|
|
68
|
+
return commits;
|
|
69
|
+
};
|
|
34
70
|
/**
|
|
35
71
|
* Gets the commits between sourceBranch and targetBranch (exclusive).
|
|
36
72
|
* - If baseCommit is provided, only returns commits after the baseCommit.
|
|
37
73
|
*/
|
|
38
74
|
export const getCommits = ({ sourceBranch, targetBranch, baseCommit }) => {
|
|
39
75
|
const baseCommitSha = parseBaseCommit(baseCommit);
|
|
40
|
-
const
|
|
41
|
-
const commits = commitsStrings.map((commitString) => parseCommit(commitString));
|
|
42
|
-
commits.reverse();
|
|
76
|
+
const commits = fetchAllBranchCommits(sourceBranch, targetBranch);
|
|
43
77
|
const baseCommitIndex = commits.findIndex((commit) => commit.sha === baseCommitSha);
|
|
44
78
|
const hasBaseCommit = baseCommitIndex !== -1;
|
|
45
79
|
if (hasBaseCommit) {
|
package/dist/bin/git.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../bin/git.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAC1C,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAiB,EAAE,EAAE;IACjD,MAAM,kBAAkB,GAAG,yBAAyB,CAChD,wBAAwB,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAChF,CAAC;IAEF,MAAM,YAAY,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,CAAC,KAAK,CAAC,6BAA6B,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,OAAO,YAAY,CAAC;AACxB,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,WAA+B,EAAsB,EAAE;IACnF,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,GAAW,CAAC;IAChB,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,GAAG,GAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAY,CAAC,GAAG,CAAC;IAClD,CAAC;SAAM,CAAC;QACJ,GAAG,GAAG,WAAW,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACX,6BAA6B,GAAG,0EAA0E,WAAW,IAAI,CAC5H,CAAC;IACN,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF
|
|
1
|
+
{"version":3,"file":"git.js","sourceRoot":"","sources":["../../bin/git.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,yBAAyB,EAAE,MAAM,YAAY,CAAC;AAEvD,MAAM,CAAC,MAAM,oBAAoB,GAAG,KAAK,CAAC;AAC1C,MAAM,cAAc,GAAG,CAAC,IAAI,EAAE,UAAU,EAAE,KAAK,EAAE,IAAI,CAAC,CAAC,IAAI,CAAC,oBAAoB,CAAC,CAAC;AAElF;;GAEG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,OAAiB,EAAE,EAAE;IACjD,MAAM,kBAAkB,GAAG,yBAAyB,CAChD,wBAAwB,OAAO,CAAC,CAAC,CAAC,CAAC,GAAG,MAAM,OAAO,CAAC,OAAO,CAAC,MAAM,GAAG,CAAC,CAAC,CAAC,GAAG,EAAE,CAChF,CAAC;IAEF,MAAM,YAAY,GAAG,kBAAkB,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACpD,OAAO,CAAC,KAAK,CAAC,6BAA6B,YAAY,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACnF,OAAO,YAAY,CAAC;AACxB,CAAC,CAAC;AAEF;;;;GAIG;AACH,MAAM,CAAC,MAAM,kBAAkB,GAAG,CAAC,YAAoB,EAAE,YAAoB,EAAW,EAAE;IACtF,MAAM,SAAS,GAAG,yBAAyB,CAAC,uCAAuC,YAAY,KAAK,YAAY,EAAE,CAAC;SAC9G,KAAK,CAAC,IAAI,CAAC;SACX,MAAM,CAAC,OAAO,CAAC,CAAC;IAErB,KAAK,MAAM,GAAG,IAAI,SAAS,EAAE,CAAC;QAC1B,MAAM,OAAO,GAAG,yBAAyB,CAAC,iCAAiC,GAAG,EAAE,CAAC,CAAC,IAAI,EAAE,CAAC,KAAK,CAAC,GAAG,CAAC,CAAC;QACpG,KAAK,MAAM,MAAM,IAAI,OAAO,EAAE,CAAC;YAC3B,kDAAkD;YAClD,6FAA6F;YAC7F,MAAM,SAAS,GAAG,yBAAyB,CAAC,kBAAkB,MAAM,IAAI,YAAY,EAAE,CAAC,CAAC;YACxF,IAAI,SAAS,KAAK,MAAM,EAAE,CAAC;gBACvB,OAAO,IAAI,CAAC;YAChB,CAAC;QACL,CAAC;IACL,CAAC;IACD,OAAO,KAAK,CAAC;AACjB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,yBAAyB,GAAG,CAAC,YAAoB,EAAE,YAAoB,EAAY,EAAE;IAC9F,MAAM,MAAM,GAAG,yBAAyB,CACpC,oDAAoD,YAAY,KAAK,YAAY,EAAE,CACtF,CAAC;IACF,OAAO,MAAM,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC,MAAM,CAAC,OAAO,CAAC,CAAC;AAC9C,CAAC,CAAC;AAEF,MAAM,SAAS,GAAG,iBAAiB,CAAC;AAEpC;;;;GAIG;AACH,MAAM,CAAC,MAAM,eAAe,GAAG,CAAC,WAA+B,EAAsB,EAAE;IACnF,IAAI,CAAC,WAAW;QAAE,OAAO,SAAS,CAAC;IACnC,IAAI,GAAW,CAAC;IAChB,IAAI,WAAW,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;QAC9B,GAAG,GAAI,IAAI,CAAC,KAAK,CAAC,WAAW,CAAY,CAAC,GAAG,CAAC;IAClD,CAAC;SAAM,CAAC;QACJ,GAAG,GAAG,WAAW,CAAC;IACtB,CAAC;IACD,IAAI,CAAC,SAAS,CAAC,IAAI,CAAC,GAAG,CAAC,EAAE,CAAC;QACvB,MAAM,IAAI,KAAK,CACX,6BAA6B,GAAG,0EAA0E,WAAW,IAAI,CAC5H,CAAC;IACN,CAAC;IACD,OAAO,GAAG,CAAC;AACf,CAAC,CAAC;AAEF,MAAM,qBAAqB,GAAG,CAAC,YAAoB,EAAE,YAAoB,EAAY,EAAE;IACnF,MAAM,cAAc,GAAG,yBAAyB,CAC5C,4BAA4B,cAAc,KAAK,YAAY,KAAK,YAAY,EAAE,CACjF,CAAC,KAAK,CAAC,IAAI,CAAC,CAAC;IACd,MAAM,OAAO,GAAG,cAAc,CAAC,GAAG,CAAC,CAAC,YAAY,EAAE,EAAE,CAAC,WAAW,CAAC,YAAY,CAAC,CAAC,CAAC;IAChF,OAAO,CAAC,OAAO,EAAE,CAAC;IAClB,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF;;;GAGG;AACH,MAAM,CAAC,MAAM,UAAU,GAAG,CAAC,EAAE,YAAY,EAAE,YAAY,EAAE,UAAU,EAAU,EAAY,EAAE;IACvF,MAAM,aAAa,GAAG,eAAe,CAAC,UAAU,CAAC,CAAC;IAClD,MAAM,OAAO,GAAG,qBAAqB,CAAC,YAAY,EAAE,YAAY,CAAC,CAAC;IAElE,MAAM,eAAe,GAAG,OAAO,CAAC,SAAS,CAAC,CAAC,MAAM,EAAE,EAAE,CAAC,MAAM,CAAC,GAAG,KAAK,aAAa,CAAC,CAAC;IAEpF,MAAM,aAAa,GAAG,eAAe,KAAK,CAAC,CAAC,CAAC;IAC7C,IAAI,aAAa,EAAE,CAAC;QAChB,MAAM,qBAAqB,GAAG,OAAO,CAAC,KAAK,CAAC,eAAe,GAAG,CAAC,CAAC,CAAC;QACjE,OAAO,CAAC,KAAK,CACT,qBAAqB,aAAa,aAAa,eAAe,eAAe,qBAAqB,CAAC,MAAM,mBAAmB,CAC/H,CAAC;QACF,OAAO,CAAC,KAAK,CAAC,2BAA2B,qBAAqB,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QAC/F,OAAO,qBAAqB,CAAC;IACjC,CAAC;IAED,OAAO,CAAC,KAAK,CACT,eAAe,aAAa,iDAAiD,OAAO,CAAC,MAAM,UAAU,CACxG,CAAC;IACF,OAAO,CAAC,KAAK,CAAC,2BAA2B,OAAO,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,EAAE,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,IAAI,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;IACjF,OAAO,OAAO,CAAC;AACnB,CAAC,CAAC;AAEF,MAAM,CAAC,MAAM,WAAW,GAAG,CAAC,YAAoB,EAAU,EAAE;IACxD,MAAM,MAAM,GAAG,YAAY,CAAC,KAAK,CAAC,oBAAoB,CAAC,CAAC;IACxD,IAAI,MAAM,CAAC,MAAM,KAAK,CAAC,EAAE,CAAC;QACtB,MAAM,IAAI,KAAK,CAAC,kCAAkC,YAAY,EAAE,CAAC,CAAC;IACtE,CAAC;IACD,MAAM,CAAC,GAAG,EAAE,MAAM,EAAE,IAAI,EAAE,OAAO,CAAC,GAAG,MAAM,CAAC;IAC5C,OAAO;QACH,GAAG;QACH,MAAM;QACN,IAAI;QACJ,OAAO;KACV,CAAC;AACN,CAAC,CAAC"}
|
package/dist/bin/main.js
CHANGED
|
@@ -5,7 +5,7 @@ import yargs from 'yargs';
|
|
|
5
5
|
import { hideBin } from 'yargs/helpers';
|
|
6
6
|
import { deleteOldBuilds, runBuilds } from './build.js';
|
|
7
7
|
import { getChangedActors } from './diff-changes.js';
|
|
8
|
-
import { getChangedFiles, getCommits } from './git.js';
|
|
8
|
+
import { getBranchOnlyChangedFiles, getChangedFiles, getCommits, hasMergeFromTarget } from './git.js';
|
|
9
9
|
import { getPushData } from './github.js';
|
|
10
10
|
import { notifyToSlack } from './slack.js';
|
|
11
11
|
import { reportTestResults } from './test-report.js';
|
|
@@ -28,6 +28,32 @@ const buildOptions = (y) => {
|
|
|
28
28
|
type: 'string',
|
|
29
29
|
});
|
|
30
30
|
};
|
|
31
|
+
const resolveChangedActors = async ({ targetBranch, sourceBranch, baseCommit }, { isLatest }) => {
|
|
32
|
+
const actorConfigs = await getRepoActors();
|
|
33
|
+
// This is an optimization for the common case where a branch only has cosmetic changes but had to merge in
|
|
34
|
+
// functional changes from master (being up-to-date is a CI requirement). Master is already validated, and
|
|
35
|
+
// since the branch has no functional changes of its own, there is nothing new to validate.
|
|
36
|
+
// Exception: if the branch has any functional changes alongside the merge, we must re-test — even
|
|
37
|
+
// individually validated changes can have novel interactions when combined.
|
|
38
|
+
if (hasMergeFromTarget(sourceBranch, targetBranch)) {
|
|
39
|
+
const branchOnlyFiles = getBranchOnlyChangedFiles(sourceBranch, targetBranch);
|
|
40
|
+
// Omit baseCommit to get full branch history. Validated functional commits can still interact with merged ones
|
|
41
|
+
const allBranchCommits = getCommits({ sourceBranch, targetBranch, baseCommit: undefined });
|
|
42
|
+
const branchOnlyActorsChanged = getChangedActors({
|
|
43
|
+
filepathsChanged: branchOnlyFiles,
|
|
44
|
+
actorConfigs,
|
|
45
|
+
commits: allBranchCommits,
|
|
46
|
+
});
|
|
47
|
+
if (branchOnlyActorsChanged.length === 0) {
|
|
48
|
+
console.error('[MERGE-FROM-TARGET-OPTIMIZATION]: Branch itself has no functional changes, skipping builds');
|
|
49
|
+
return [];
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
// If the optimization doesn't apply, we check all branch commits including merges for full coverage. We don't reuse the merge optimization results because here we can apply baseCommit and check merge commits (they might be functional or just cosmetic)
|
|
53
|
+
const commits = getCommits({ targetBranch, sourceBranch, baseCommit });
|
|
54
|
+
const changedFiles = getChangedFiles(commits);
|
|
55
|
+
return getChangedActors({ filepathsChanged: changedFiles, actorConfigs, isLatest, commits });
|
|
56
|
+
};
|
|
31
57
|
await yargs()
|
|
32
58
|
.scriptName('public-actors-utils')
|
|
33
59
|
.option('dry-run', {
|
|
@@ -57,16 +83,8 @@ await yargs()
|
|
|
57
83
|
const actorConfigs = await getRepoActors();
|
|
58
84
|
console.log(JSON.stringify(actorConfigs));
|
|
59
85
|
})
|
|
60
|
-
.command('get-affected-actors', '', buildOptions, async (
|
|
61
|
-
const
|
|
62
|
-
const changedFiles = getChangedFiles(commits);
|
|
63
|
-
const actorConfigs = await getRepoActors();
|
|
64
|
-
const actorsChanged = getChangedActors({
|
|
65
|
-
filepathsChanged: changedFiles,
|
|
66
|
-
actorConfigs,
|
|
67
|
-
isLatest: false,
|
|
68
|
-
commits,
|
|
69
|
-
});
|
|
86
|
+
.command('get-affected-actors', '', buildOptions, async ({ targetBranch, sourceBranch, baseCommit }) => {
|
|
87
|
+
const actorsChanged = await resolveChangedActors({ targetBranch, sourceBranch, baseCommit }, { isLatest: false });
|
|
70
88
|
console.log(JSON.stringify(actorsChanged));
|
|
71
89
|
})
|
|
72
90
|
.command('report-tests', '', (args) => args
|
|
@@ -76,23 +94,16 @@ await yargs()
|
|
|
76
94
|
.option('workflow-name', { type: 'string' }), async (args) => {
|
|
77
95
|
await reportTestResults(args);
|
|
78
96
|
})
|
|
79
|
-
.command('build', '', (args) => buildOptions(args).option('dry-run', { type: 'boolean', default: false }), async (
|
|
80
|
-
const
|
|
81
|
-
const changedFiles = getChangedFiles(commits);
|
|
82
|
-
const actorConfigs = await getRepoActors();
|
|
83
|
-
const actorsChanged = getChangedActors({
|
|
84
|
-
filepathsChanged: changedFiles,
|
|
85
|
-
actorConfigs,
|
|
86
|
-
commits,
|
|
87
|
-
});
|
|
97
|
+
.command('build', '', (args) => buildOptions(args).option('dry-run', { type: 'boolean', default: false }), async ({ targetBranch, sourceBranch, baseCommit, dryRun }) => {
|
|
98
|
+
const actorsChanged = await resolveChangedActors({ targetBranch, sourceBranch, baseCommit }, { isLatest: false });
|
|
88
99
|
// https://github.com/apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
|
|
89
100
|
// git@github.com:apify-store/google-maps#:actors/lukaskrivka_google-maps-with-contact-details
|
|
90
101
|
const repoUrl = spawnCommandInGhWorkspace(`git remote get-url origin`).replace(/^https:\/\/github\.com\//, 'git@github.com:');
|
|
91
102
|
const builds = await runBuilds({
|
|
92
103
|
repoUrl,
|
|
93
104
|
actorConfigs: actorsChanged,
|
|
94
|
-
branch:
|
|
95
|
-
dryRun
|
|
105
|
+
branch: sourceBranch.replace('origin/', ''),
|
|
106
|
+
dryRun,
|
|
96
107
|
});
|
|
97
108
|
console.log(JSON.stringify(builds));
|
|
98
109
|
})
|
package/dist/bin/main.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../bin/main.ts"],"names":[],"mappings":";AAEA,OAAO,OAAO,MAAM,cAAc,CAAC;AAEnC,OAAO,KAAoB,MAAM,OAAO,CAAC;AACzC,gFAAgF;AAChF,OAAO,EAAE,OAAO,EAAE,MAAM,eAAe,CAAC;AAExC,OAAO,EAAE,eAAe,EAAE,SAAS,EAAE,MAAM,YAAY,CAAC;AACxD,OAAO,EAAE,gBAAgB,EAAE,MAAM,mBAAmB,CAAC;AACrD,OAAO,EAAE,eAAe,EAAE,UAAU,EAAE,MAAM,UAAU,CAAC;
|
|
1
|
+
{"version":3,"file":"main.js","sourceRoot":"","sources":["../../bin/main.ts"],"names":[],"mappings":"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"}
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { beforeEach, describe, expect, it, vi } from 'vitest';
|
|
2
|
-
import { getChangedFiles, getCommits, parseBaseCommit } from '../../../bin/git.js';
|
|
2
|
+
import { getBranchOnlyChangedFiles, getChangedFiles, getCommits, hasMergeFromTarget, parseBaseCommit, } from '../../../bin/git.js';
|
|
3
3
|
import * as Utils from '../../../bin/utils.js';
|
|
4
4
|
describe('getCommits', () => {
|
|
5
5
|
const sourceBranch = 'feature-branch';
|
|
@@ -84,6 +84,73 @@ describe('getChangedFiles', () => {
|
|
|
84
84
|
expect(gitCommandSpy).toHaveBeenCalledWith(`git diff --name-only ${onlySha}~..${onlySha}`);
|
|
85
85
|
});
|
|
86
86
|
});
|
|
87
|
+
describe('hasMergeFromTarget', () => {
|
|
88
|
+
const sourceBranch = 'feature-branch';
|
|
89
|
+
const targetBranch = 'main';
|
|
90
|
+
const mergeSha = 'f'.repeat(40);
|
|
91
|
+
const branchParentSha = 'b'.repeat(40);
|
|
92
|
+
const targetParentSha = 't'.repeat(40);
|
|
93
|
+
let gitCommandSpy;
|
|
94
|
+
beforeEach(() => {
|
|
95
|
+
gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
|
|
96
|
+
});
|
|
97
|
+
it('should return false when there are no merge commits on the branch', () => {
|
|
98
|
+
gitCommandSpy.mockImplementation((cmd) => {
|
|
99
|
+
if (cmd.includes('--merges'))
|
|
100
|
+
return '';
|
|
101
|
+
return '';
|
|
102
|
+
});
|
|
103
|
+
expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(false);
|
|
104
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(`git log --merges --pretty=format:%H ${targetBranch}..${sourceBranch}`);
|
|
105
|
+
});
|
|
106
|
+
it('should return true when a merge commit has a parent reachable from targetBranch', () => {
|
|
107
|
+
gitCommandSpy.mockImplementation((cmd) => {
|
|
108
|
+
if (cmd.includes('--merges'))
|
|
109
|
+
return mergeSha;
|
|
110
|
+
if (cmd.includes('--pretty=format:%P'))
|
|
111
|
+
return `${branchParentSha} ${targetParentSha}`;
|
|
112
|
+
if (cmd.startsWith(`git merge-base ${branchParentSha}`))
|
|
113
|
+
return branchParentSha; // not ancestor
|
|
114
|
+
if (cmd.startsWith(`git merge-base ${targetParentSha}`))
|
|
115
|
+
return targetParentSha; // is ancestor
|
|
116
|
+
return '';
|
|
117
|
+
});
|
|
118
|
+
expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(true);
|
|
119
|
+
});
|
|
120
|
+
it('should return false when the merge commit parent is not reachable from targetBranch (unrelated branch merge)', () => {
|
|
121
|
+
const unrelatedSha = 'e'.repeat(40);
|
|
122
|
+
const differentMergeBase = '0'.repeat(40);
|
|
123
|
+
gitCommandSpy.mockImplementation((cmd) => {
|
|
124
|
+
if (cmd.includes('--merges'))
|
|
125
|
+
return mergeSha;
|
|
126
|
+
if (cmd.includes('--pretty=format:%P'))
|
|
127
|
+
return `${branchParentSha} ${unrelatedSha}`;
|
|
128
|
+
// merge-base returns something other than the parent — not an ancestor
|
|
129
|
+
if (cmd.startsWith('git merge-base'))
|
|
130
|
+
return differentMergeBase;
|
|
131
|
+
return '';
|
|
132
|
+
});
|
|
133
|
+
expect(hasMergeFromTarget(sourceBranch, targetBranch)).toBe(false);
|
|
134
|
+
});
|
|
135
|
+
});
|
|
136
|
+
describe('getBranchOnlyChangedFiles', () => {
|
|
137
|
+
const sourceBranch = 'feature-branch';
|
|
138
|
+
const targetBranch = 'main';
|
|
139
|
+
let gitCommandSpy;
|
|
140
|
+
beforeEach(() => {
|
|
141
|
+
gitCommandSpy = vi.spyOn(Utils, 'spawnCommandInGhWorkspace');
|
|
142
|
+
});
|
|
143
|
+
it('should return files touched by non-merge commits', () => {
|
|
144
|
+
gitCommandSpy.mockReturnValue('README.md\n\nactors/foo_bar/src/main.ts\n');
|
|
145
|
+
const result = getBranchOnlyChangedFiles(sourceBranch, targetBranch);
|
|
146
|
+
expect(result).toStrictEqual(['README.md', 'actors/foo_bar/src/main.ts']);
|
|
147
|
+
expect(gitCommandSpy).toHaveBeenCalledWith(`git log --no-merges --name-only --pretty=format: ${targetBranch}..${sourceBranch}`);
|
|
148
|
+
});
|
|
149
|
+
it('should return empty array when there are no non-merge commits', () => {
|
|
150
|
+
gitCommandSpy.mockReturnValue('');
|
|
151
|
+
expect(getBranchOnlyChangedFiles(sourceBranch, targetBranch)).toStrictEqual([]);
|
|
152
|
+
});
|
|
153
|
+
});
|
|
87
154
|
const VALID_SHA = 'a'.repeat(40);
|
|
88
155
|
const VALID_JSON = JSON.stringify({ sha: VALID_SHA, author: 'test', date: 'now', message: 'msg' });
|
|
89
156
|
describe('parseBaseCommit', () => {
|