@applitools/core 4.70.2 → 4.71.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 CHANGED
@@ -1,5 +1,54 @@
1
1
  # Changelog
2
2
 
3
+ ## [4.71.0](https://github.com/Applitools-Dev/sdk/compare/js/core@4.70.3...js/core@4.71.0) (2026-10-01)
4
+
5
+
6
+ ### Features
7
+
8
+ * detect git cherry-picks and report them in StartSession | AD-16372 ([#4235](https://github.com/Applitools-Dev/sdk/issues/4235)) ([92db51e](https://github.com/Applitools-Dev/sdk/commit/92db51e3ea713ee7c59be334d8e11d2c312d1095))
9
+
10
+
11
+ ### Dependencies
12
+
13
+ * @applitools/ufg-client bumped to 1.23.5
14
+ #### Bug Fixes
15
+
16
+ * add a deadline for reading a resource body | FLD-4198 ([#4247](https://github.com/Applitools-Dev/sdk/issues/4247)) ([a9faac2](https://github.com/Applitools-Dev/sdk/commit/a9faac2ade1b851e6a00f5bc4a3990f10a345541))
17
+ * @applitools/nml-client bumped to 1.12.6
18
+
19
+ * @applitools/core-base bumped to 1.40.0
20
+ #### Features
21
+
22
+ * detect git cherry-picks and report them in StartSession | AD-16372 ([#4235](https://github.com/Applitools-Dev/sdk/issues/4235)) ([92db51e](https://github.com/Applitools-Dev/sdk/commit/92db51e3ea713ee7c59be334d8e11d2c312d1095))
23
+ * @applitools/ec-client bumped to 1.12.49
24
+
25
+
26
+ ## [4.70.3](https://github.com/Applitools-Dev/sdk/compare/js/core@4.70.2...js/core@4.70.3) (2026-09-27)
27
+
28
+
29
+ ### Dependencies
30
+
31
+ * @applitools/driver bumped to 1.26.11
32
+ #### Bug Fixes
33
+
34
+ * fail executePoll when the target stops responding | AD-16554 ([#4227](https://github.com/Applitools-Dev/sdk/issues/4227)) ([39745d1](https://github.com/Applitools-Dev/sdk/commit/39745d117942122a364d0a598a48b205838a5ca6))
35
+ * @applitools/dom-snapshot bumped to 4.18.4
36
+
37
+ * @applitools/spec-driver-webdriver bumped to 1.6.11
38
+
39
+ * @applitools/spec-driver-selenium bumped to 1.8.11
40
+
41
+ * @applitools/spec-driver-playwright bumped to 1.9.11
42
+
43
+ * @applitools/spec-driver-puppeteer bumped to 1.8.11
44
+
45
+ * @applitools/screenshoter bumped to 3.12.33
46
+
47
+ * @applitools/nml-client bumped to 1.12.5
48
+
49
+ * @applitools/ec-client bumped to 1.12.48
50
+
51
+
3
52
  ## [4.70.2](https://github.com/Applitools-Dev/sdk/compare/js/core@4.70.1...js/core@4.70.2) (2026-09-17)
4
53
 
5
54
 
package/dist/open-eyes.js CHANGED
@@ -226,7 +226,7 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
226
226
  }
227
227
  };
228
228
  async function handleBranchFallbackList(account, settings, logger) {
229
- var _a, _b;
229
+ var _a, _b, _c;
230
230
  const scm = getScmSettings(account.scmSettings);
231
231
  const integrationActive = !settings.ignoreGitBranching && scm.lookupFallbackList;
232
232
  // Extract gitBranchName (the actual git branch name)
@@ -264,6 +264,18 @@ function makeOpenEyes({ type: defaultType = 'classic', clients, batch, removeDup
264
264
  logger,
265
265
  });
266
266
  settings.branchLookupFallbackList = outcome.list;
267
+ // Walks from the same tip as latestCommitInfo (the PR head on pull_request events): the server compares the
268
+ // pick positions with a cutoff taken from it.
269
+ if (((_c = outcome.list) === null || _c === void 0 ? void 0 : _c.length) && settings.latestCommitInfo) {
270
+ const cherryPicks = await (0, extract_git_info_1.extractCherryPicks)({
271
+ headSha: settings.latestCommitInfo.sha,
272
+ fallbackBranchNames: outcome.list.map(branch => branch.branchName),
273
+ execOptions: { cwd },
274
+ logger,
275
+ });
276
+ if (cherryPicks === null || cherryPicks === void 0 ? void 0 : cherryPicks.length)
277
+ settings.cherryPicks = cherryPicks;
278
+ }
267
279
  if (outcome.status === 'precondition-failure') {
268
280
  reportScmPreconditionFailure({
269
281
  key: scmPreconditionKey(cwd, settings.gitBranchName),
@@ -26,7 +26,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
26
26
  return (mod && mod.__esModule) ? mod : { "default": mod };
27
27
  };
28
28
  Object.defineProperty(exports, "__esModule", { value: true });
29
- exports.extractBranchLookupFallbackList = exports.getBranchAncestryByLocalBranches = exports.getBranchAncestryByCommits = exports.isISODate = exports.extractBranchingTimestamp = exports.extractBuildIdFromCI = exports.extractCIBranchName = exports.extractGitRepo = exports.extractGitBranch = exports.extractLatestCommitInfo = exports.extractDefaultBranch = exports.gitEnvironmentFindings = exports.checkGitEnvironment = exports.resolveBranchToLocalRef = exports.getPrimaryRemoteName = exports.cacheKey = void 0;
29
+ exports.extractCherryPicks = exports.extractBranchLookupFallbackList = exports.getBranchAncestryByLocalBranches = exports.getBranchAncestryByCommits = exports.isISODate = exports.extractBranchingTimestamp = exports.extractBuildIdFromCI = exports.extractCIBranchName = exports.extractGitRepo = exports.extractGitBranch = exports.extractLatestCommitInfo = exports.extractDefaultBranch = exports.gitEnvironmentFindings = exports.checkGitEnvironment = exports.resolveBranchToLocalRef = exports.getPrimaryRemoteName = exports.cacheKey = void 0;
30
30
  const utils = __importStar(require("@applitools/utils"));
31
31
  const fs_1 = __importDefault(require("fs"));
32
32
  const path_1 = __importDefault(require("path"));
@@ -1703,3 +1703,131 @@ exports.extractBranchLookupFallbackList = Object.assign(async function (options)
1703
1703
  withDiagnostics: (options) => computeBranchLookupFallbackList(options),
1704
1704
  clearCache: computeBranchLookupFallbackList.clearCache,
1705
1705
  });
1706
+ // The server considers at most 50 picks per session.
1707
+ const MAX_CHERRY_PICKS = 50;
1708
+ const CHERRY_PICK_GIT_CONCURRENCY = 10;
1709
+ // Written by `git cherry-pick -x`; a pick of a pick carries one line per hop, the original first.
1710
+ const CHERRY_PICK_LINE = /^\(cherry picked from commit ([0-9a-f]{7,64})\)$/m;
1711
+ function cherryPickLimit(name, defaultValue) {
1712
+ const value = utils.general.getEnvValue(name, 'number');
1713
+ return Number.isInteger(value) && value >= 0 ? value : defaultValue;
1714
+ }
1715
+ /** The full sha of `rev`, or `undefined` when it is not in the local history. Throws on a git error. */
1716
+ async function resolveCommit(git, rev) {
1717
+ const result = await git(['rev-parse', '--verify', '--quiet', `${rev}^{commit}`]);
1718
+ const found = classifyGitPredicate(result);
1719
+ if (found === 'error')
1720
+ throw new Error(`git rev-parse ${rev} failed`);
1721
+ return found ? result.stdout.trim() : undefined;
1722
+ }
1723
+ async function resolveFallbackBranch(git, remoteName, name) {
1724
+ for (const ref of [`${remoteName}/${name}`, name]) {
1725
+ if (await resolveCommit(git, ref))
1726
+ return { name, ref };
1727
+ }
1728
+ throw new Error(`fallback branch "${name}" does not resolve to a commit`);
1729
+ }
1730
+ function parsePickCandidate(record) {
1731
+ var _a;
1732
+ const [pickedSha, pickedCommitTime, body = ''] = record.replace(/^\n/, '').split('\x1f');
1733
+ // Hand-edited lines are not validated; the first line is trusted.
1734
+ const originalSha = (_a = body.match(CHERRY_PICK_LINE)) === null || _a === void 0 ? void 0 : _a[1];
1735
+ return originalSha ? { pickedSha, pickedCommitTime, originalSha } : undefined;
1736
+ }
1737
+ async function collectPickCandidates(git, branches, headSha, logger) {
1738
+ const maxCommits = cherryPickLimit('CHERRY_PICK_MAX_COMMITS', 1000);
1739
+ const recordsPerBranch = await parallelWithLimit(branches, CHERRY_PICK_GIT_CONCURRENCY, async (branch) => {
1740
+ const range = `${branch.ref}..${headSha}`;
1741
+ // The pick time is when it landed on this branch: the committer date (a pick keeps the original author date).
1742
+ const log = await git(['log', `--max-count=${maxCommits}`, '--format=%H%x1f%cI%x1f%B%x1e', range]);
1743
+ if (log.code !== 0)
1744
+ throw new Error(`git log ${range} failed: ${log.stderr.trim()}`);
1745
+ const records = log.stdout.split('\x1e').filter(record => record.trim());
1746
+ if (records.length === maxCommits)
1747
+ logger.log(`Cherry-pick detection read only the newest ${maxCommits} commits of ${range}`);
1748
+ return records;
1749
+ });
1750
+ const candidates = new Map();
1751
+ for (const records of recordsPerBranch) {
1752
+ for (const record of records) {
1753
+ const candidate = parsePickCandidate(record);
1754
+ if (candidate && !candidates.has(candidate.pickedSha))
1755
+ candidates.set(candidate.pickedSha, candidate);
1756
+ }
1757
+ }
1758
+ return Array.from(candidates.values());
1759
+ }
1760
+ function newestCandidates(candidates, logger) {
1761
+ if (candidates.length > MAX_CHERRY_PICKS) {
1762
+ logger.log(`Cherry-pick detection checks only the newest ${MAX_CHERRY_PICKS} of ${candidates.length} picks`);
1763
+ }
1764
+ return [...candidates]
1765
+ .sort((a, b) => epochOrOldest(b.pickedCommitTime) - epochOrOldest(a.pickedCommitTime))
1766
+ .slice(0, MAX_CHERRY_PICKS);
1767
+ }
1768
+ /** The first fallback branch containing `sha` — the server resolves a sha on several branches the same way. */
1769
+ async function findContainingBranch(git, sha, branches) {
1770
+ for (const branch of branches) {
1771
+ const contains = classifyGitPredicate(await git(['merge-base', '--is-ancestor', sha, branch.ref]));
1772
+ if (contains === 'error')
1773
+ throw new Error(`git merge-base --is-ancestor ${sha} ${branch.ref} failed`);
1774
+ if (contains)
1775
+ return branch.name;
1776
+ }
1777
+ return undefined;
1778
+ }
1779
+ async function resolvePick(git, candidate, branches, logger) {
1780
+ const originalSha = await resolveCommit(git, candidate.originalSha);
1781
+ if (!originalSha) {
1782
+ logger.log(`Cherry-pick ${candidate.pickedSha} dropped: original ${candidate.originalSha} is not in the local history`);
1783
+ return undefined;
1784
+ }
1785
+ const originalBranch = await findContainingBranch(git, originalSha, branches);
1786
+ if (!originalBranch) {
1787
+ logger.log(`Cherry-pick ${candidate.pickedSha} dropped: original ${originalSha} is not on a fallback-list branch`);
1788
+ return undefined;
1789
+ }
1790
+ const shown = await git(['show', '-s', '--format=%aI', originalSha]);
1791
+ const originalCommitTime = shown.stdout.trim();
1792
+ if (shown.code !== 0 || !isISODate(originalCommitTime))
1793
+ throw new Error(`no commit time for ${originalSha}`);
1794
+ return {
1795
+ pickedSha: candidate.pickedSha,
1796
+ pickedCommitTime: candidate.pickedCommitTime,
1797
+ originalBranch,
1798
+ originalSha,
1799
+ originalCommitTime,
1800
+ };
1801
+ }
1802
+ async function detectCherryPicks({ headSha, fallbackBranchNames, execOptions, logger = (0, logger_1.makeLogger)(), }) {
1803
+ const timeout = cherryPickLimit('CHERRY_PICK_GIT_TIMEOUT', 10000);
1804
+ const git = args => executeWithLog((0, shell_quote_1.quote)(['git', ...args]), { execOptions: { ...execOptions, timeout }, logger });
1805
+ try {
1806
+ const remoteName = await (0, exports.getPrimaryRemoteName)({ execOptions, logger });
1807
+ const branches = await parallelWithLimit(fallbackBranchNames, CHERRY_PICK_GIT_CONCURRENCY, name => resolveFallbackBranch(git, remoteName, name));
1808
+ const candidates = await collectPickCandidates(git, branches, headSha, logger);
1809
+ const resolved = await parallelWithLimit(newestCandidates(candidates, logger), CHERRY_PICK_GIT_CONCURRENCY, candidate => resolvePick(git, candidate, branches, logger));
1810
+ const picks = resolved.filter((pick) => Boolean(pick));
1811
+ logger.log('Detected cherry-picks', JSON.stringify(picks));
1812
+ return picks;
1813
+ }
1814
+ catch (err) {
1815
+ // Any git error means nothing is sent: a partial list would look like "no more picks".
1816
+ logger.log(`Cherry-pick detection skipped: ${err instanceof Error ? err.message : err}`);
1817
+ return undefined;
1818
+ }
1819
+ }
1820
+ /**
1821
+ * `git cherry-pick -x` commits between `headSha` and the fork point with each fallback-list branch, resolved to
1822
+ * the original commit and kept only when a fallback-list branch contains it. Newest first.
1823
+ *
1824
+ * @returns `[]` when there are no picks, `undefined` when detection could not run.
1825
+ */
1826
+ exports.extractCherryPicks = utils.general.cachify(detectCherryPicks, args => {
1827
+ var _a;
1828
+ return ({
1829
+ headSha: args[0].headSha,
1830
+ fallbackBranchNames: args[0].fallbackBranchNames.join('\n'),
1831
+ cwd: (_a = args[0].execOptions) === null || _a === void 0 ? void 0 : _a.cwd,
1832
+ });
1833
+ });
package/package.json CHANGED
@@ -1,15 +1,7 @@
1
1
  {
2
2
  "name": "@applitools/core",
3
- "version": "4.70.2",
3
+ "version": "4.71.0",
4
4
  "homepage": "https://applitools.com",
5
- "bugs": {
6
- "url": "https://github.com/applitools/eyes.sdk.javascript1/issues"
7
- },
8
- "repository": {
9
- "type": "git",
10
- "url": "git://github.com/applitools/eyes.sdk.javascript1.git",
11
- "directory": "js/packages/core-base"
12
- },
13
5
  "license": "SEE LICENSE IN LICENSE",
14
6
  "author": {
15
7
  "name": "Applitools Team",
@@ -63,19 +55,19 @@
63
55
  "test:ios-simulator": "MOCHA_ONLY_TAGS=ios-simulator MOCHA_GROUP=ios-simulator run --top-level mocha ./test/e2e/classic/webview.spec.ts --exit --require ./test/mocha-global-setup.js"
64
56
  },
65
57
  "dependencies": {
66
- "@applitools/core-base": "1.39.1",
58
+ "@applitools/core-base": "1.40.0",
67
59
  "@applitools/dom-capture": "11.8.6",
68
- "@applitools/dom-snapshot": "4.18.3",
69
- "@applitools/driver": "1.26.10",
70
- "@applitools/ec-client": "1.12.47",
60
+ "@applitools/dom-snapshot": "4.18.4",
61
+ "@applitools/driver": "1.26.11",
62
+ "@applitools/ec-client": "1.12.49",
71
63
  "@applitools/image": "1.3.3",
72
64
  "@applitools/logger": "2.3.1",
73
- "@applitools/nml-client": "1.12.4",
65
+ "@applitools/nml-client": "1.12.6",
74
66
  "@applitools/req": "1.11.3",
75
- "@applitools/screenshoter": "3.12.32",
67
+ "@applitools/screenshoter": "3.12.33",
76
68
  "@applitools/snippets": "2.9.7",
77
69
  "@applitools/socket": "1.3.17",
78
- "@applitools/ufg-client": "1.23.4",
70
+ "@applitools/ufg-client": "1.23.5",
79
71
  "@applitools/utils": "1.15.2",
80
72
  "abort-controller": "3.0.0",
81
73
  "chalk": "4.1.2",
@@ -87,10 +79,10 @@
87
79
  },
88
80
  "devDependencies": {
89
81
  "@applitools/bongo": "^5.10.0",
90
- "@applitools/spec-driver-playwright": "^1.9.10",
91
- "@applitools/spec-driver-puppeteer": "^1.8.10",
92
- "@applitools/spec-driver-selenium": "^1.8.10",
93
- "@applitools/spec-driver-webdriver": "^1.6.10",
82
+ "@applitools/spec-driver-playwright": "^1.9.11",
83
+ "@applitools/spec-driver-puppeteer": "^1.8.11",
84
+ "@applitools/spec-driver-selenium": "^1.8.11",
85
+ "@applitools/spec-driver-webdriver": "^1.6.11",
94
86
  "@applitools/test-server": "^1.4.7",
95
87
  "@applitools/test-utils": "^1.5.17",
96
88
  "@applitools/tunnel-client": "^1.12.4",
@@ -1,6 +1,7 @@
1
1
  /// <reference types="node" />
2
2
  import type { ExecOptions } from 'child_process';
3
3
  import { type Logger } from '@applitools/logger';
4
+ import type { CherryPick } from '@applitools/core-base';
4
5
  type Options = {
5
6
  execOptions?: ExecOptions;
6
7
  logger: Logger;
@@ -254,4 +255,23 @@ export declare const extractBranchLookupFallbackList: ((options: BranchLookupFal
254
255
  withDiagnostics: (options: BranchLookupFallbackListOptions) => Promise<BranchLookupOutcome>;
255
256
  clearCache: () => void;
256
257
  };
258
+ type CherryPicksOptions = {
259
+ headSha: string;
260
+ fallbackBranchNames: string[];
261
+ execOptions?: ExecOptions;
262
+ logger?: Logger;
263
+ };
264
+ declare function detectCherryPicks({ headSha, fallbackBranchNames, execOptions, logger, }: CherryPicksOptions): Promise<CherryPick[] | undefined>;
265
+ /**
266
+ * `git cherry-pick -x` commits between `headSha` and the fork point with each fallback-list branch, resolved to
267
+ * the original commit and kept only when a fallback-list branch contains it. Newest first.
268
+ *
269
+ * @returns `[]` when there are no picks, `undefined` when detection could not run.
270
+ */
271
+ export declare const extractCherryPicks: typeof detectCherryPicks & {
272
+ getCachedValues(): Promise<CherryPick[] | undefined>[];
273
+ setCachedValue(key: any, value: Promise<CherryPick[] | undefined>): void;
274
+ clearCache(): void;
275
+ TTL?: number | undefined;
276
+ };
257
277
  export {};