@gfxlabs/third-eye-cli 3.24.3 → 3.25.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/dist/bin.mjs CHANGED
@@ -184,7 +184,8 @@ z$1.object({
184
184
  storyName: z$1.string().optional(),
185
185
  storyId: z$1.string().optional(),
186
186
  storyArgs: z$1.record(z$1.string(), z$1.unknown()).optional(),
187
- tags: z$1.array(z$1.string()).optional()
187
+ tags: z$1.array(z$1.string()).optional(),
188
+ designUrl: z$1.string().optional()
188
189
  });
189
190
  //#endregion
190
191
  //#region src/config.ts
@@ -1035,7 +1036,8 @@ const generateStorybookShotItems = (baseUrl, stories, mask, modeBreakpoints, bro
1035
1036
  componentPath: story.kind,
1036
1037
  storyName: story.story,
1037
1038
  storyId: story.id,
1038
- storyArgs: story.parameters?.thirdeye?.args
1039
+ storyArgs: story.parameters?.thirdeye?.args,
1040
+ designUrl: (story.parameters?.design)?.url
1039
1041
  };
1040
1042
  const storyLevelBreakpoints = story.parameters?.thirdeye?.breakpoints ?? [];
1041
1043
  const breakpoints = selectBreakpoints(config.breakpoints, modeBreakpoints, storyLevelBreakpoints);
@@ -1640,6 +1642,12 @@ const createShots = async (turboSnapFilter) => {
1640
1642
  //#region src/git.ts
1641
1643
  const INITIAL_BATCH_SIZE = 20;
1642
1644
  /**
1645
+ * Check if a commit exists in the local git index.
1646
+ */
1647
+ const commitExists = (commit) => {
1648
+ return execGit(`git cat-file -t ${commit}`) === "commit";
1649
+ };
1650
+ /**
1643
1651
  * Execute a git command and return the trimmed output.
1644
1652
  */
1645
1653
  const execGit = (command) => {
@@ -1652,34 +1660,28 @@ const execGit = (command) => {
1652
1660
  return "";
1653
1661
  }
1654
1662
  };
1655
- /**
1656
- * Find the "covering" set of ancestor commits that have builds on the server.
1657
- *
1658
- * This mirrors Chromatic's approach:
1659
- * 1. Walk git history with `git rev-list`
1660
- * 2. Ask the server which commits have builds
1661
- * 3. Use `--not` to exclude ancestors of commits with builds
1662
- * 4. Repeat with exponentially larger batches until no more uncovered commits
1663
- *
1664
- * The result is the minimal set of ancestor commits with builds such that
1665
- * every ancestor of HEAD either has a build or is an ancestor of a commit
1666
- * with a build.
1667
- */
1668
- const getParentCommits = async (hasBuildsWithCommits) => {
1663
+ const getParentCommits = async (hasBuildsWithCommits, options) => {
1669
1664
  if (!execGit("git rev-parse HEAD")) {
1670
1665
  log.process("info", "general", "Not a git repository, skipping ancestor detection");
1671
- return [];
1666
+ return {
1667
+ parentCommits: [],
1668
+ visitedCommitsWithoutBuilds: []
1669
+ };
1672
1670
  }
1673
1671
  if (!execGit("git --no-pager log -n 1 --skip=1 --format=\"%H\"")) {
1674
1672
  log.process("info", "general", "Initial commit, no ancestors");
1675
- return [];
1673
+ return {
1674
+ parentCommits: [],
1675
+ visitedCommitsWithoutBuilds: []
1676
+ };
1676
1677
  }
1677
- let commitsWithBuilds = [];
1678
+ const sinceArg = options?.firstCommittedAtSeconds ? `--since ${options.firstCommittedAtSeconds}` : "";
1679
+ let commitsWithBuilds = [...options?.initialCommitsWithBuilds ?? []];
1678
1680
  let commitsWithoutBuilds = [];
1679
1681
  let limit = INITIAL_BATCH_SIZE;
1680
1682
  for (;;) {
1681
1683
  const notArgs = commitsWithBuilds.map((c) => c.trim()).join(" ");
1682
- const output = execGit(`git rev-list HEAD -n ${limit + commitsWithoutBuilds.length}${notArgs ? ` --not ${notArgs}` : ""}`);
1684
+ const output = execGit(`git rev-list HEAD ${sinceArg} -n ${limit + commitsWithoutBuilds.length}${notArgs ? ` --not ${notArgs}` : ""}`);
1683
1685
  const candidates = (output ? output.split("\n").filter(Boolean) : []).filter((c) => !commitsWithBuilds.includes(c)).filter((c) => !commitsWithoutBuilds.includes(c)).slice(0, limit);
1684
1686
  if (candidates.length === 0) break;
1685
1687
  log.process("info", "general", `🔍 Checking ${candidates.length} commits for builds (batch size ${limit})`);
@@ -1695,7 +1697,10 @@ const getParentCommits = async (hasBuildsWithCommits) => {
1695
1697
  }
1696
1698
  if (commitsWithBuilds.length === 0) {
1697
1699
  log.process("info", "general", "No ancestor builds found — this may be the first build");
1698
- return [];
1700
+ return {
1701
+ parentCommits: [],
1702
+ visitedCommitsWithoutBuilds: commitsWithoutBuilds
1703
+ };
1699
1704
  }
1700
1705
  if (commitsWithBuilds.length > 1) {
1701
1706
  const parentArgs = commitsWithBuilds.map((c) => `"${c}^@"`).join(" ");
@@ -1704,7 +1709,31 @@ const getParentCommits = async (hasBuildsWithCommits) => {
1704
1709
  if (maxCommits.length > 0) commitsWithBuilds = maxCommits;
1705
1710
  }
1706
1711
  log.process("info", "general", `📌 Found ${commitsWithBuilds.length} ancestor build(s): ${commitsWithBuilds.map((c) => c.slice(0, 7)).join(", ")}`);
1707
- return commitsWithBuilds;
1712
+ return {
1713
+ parentCommits: commitsWithBuilds,
1714
+ visitedCommitsWithoutBuilds: commitsWithoutBuilds
1715
+ };
1716
+ };
1717
+ /**
1718
+ * Compute the merge base commit between the current HEAD and a base branch,
1719
+ * plus a list of ancestor commits on the base branch from that point backwards.
1720
+ * This gives the server enough git history to walk back and find a build
1721
+ * even if the exact merge base commit doesn't have one.
1722
+ */
1723
+ const getMergeBaseInfo = (baseBranch) => {
1724
+ const mergeBaseCommit = execGit(`git merge-base HEAD ${execGit(`git rev-parse origin/${baseBranch}`) ? `origin/${baseBranch}` : baseBranch}`);
1725
+ if (!mergeBaseCommit) {
1726
+ log.process("info", "general", `Could not compute merge base with ${baseBranch}`);
1727
+ return null;
1728
+ }
1729
+ log.process("info", "general", `📍 Merge base with ${baseBranch}: ${mergeBaseCommit.slice(0, 7)}`);
1730
+ const ancestorsOutput = execGit(`git rev-list ${mergeBaseCommit} -n 50`);
1731
+ const mergeBaseAncestors = ancestorsOutput ? ancestorsOutput.split("\n").filter(Boolean) : [];
1732
+ log.process("info", "general", `📍 Got ${mergeBaseAncestors.length} merge base ancestors for build lookup`);
1733
+ return {
1734
+ mergeBaseCommit,
1735
+ mergeBaseAncestors
1736
+ };
1708
1737
  };
1709
1738
  //#endregion
1710
1739
  //#region ../shared/dist/client.js
@@ -1775,7 +1804,7 @@ const getApiToken = async (config) => {
1775
1804
  process.exit(1);
1776
1805
  }
1777
1806
  };
1778
- const sendInitToAPI = async (config, apiToken, parentCommits) => {
1807
+ const sendInitToAPI = async (config, apiToken, parentCommits, mergeBaseInfo) => {
1779
1808
  const client = createClient(config.thirdEyePlatform, void 0, apiToken);
1780
1809
  return withRetry("init", () => client.orgs.projects.builds.init({
1781
1810
  orgId: config.thirdEyeOrgId,
@@ -1785,7 +1814,9 @@ const sendInitToAPI = async (config, apiToken, parentCommits) => {
1785
1814
  buildNumber: config.ciBuildNumber,
1786
1815
  baseBranch: config.baseBranch || void 0,
1787
1816
  prNumber: config.prNumber,
1788
- parentCommits
1817
+ parentCommits,
1818
+ mergeBaseCommit: mergeBaseInfo?.mergeBaseCommit,
1819
+ mergeBaseAncestors: mergeBaseInfo?.mergeBaseAncestors
1789
1820
  }));
1790
1821
  };
1791
1822
  const sendHasBuildsWithCommitsToAPI = async (config, apiToken, commits) => {
@@ -1796,6 +1827,22 @@ const sendHasBuildsWithCommitsToAPI = async (config, apiToken, commits) => {
1796
1827
  commits
1797
1828
  }))).commits;
1798
1829
  };
1830
+ const getGitInfoFromAPI = async (config, apiToken, branch) => {
1831
+ const client = createClient(config.thirdEyePlatform, void 0, apiToken);
1832
+ return withRetry("getGitInfo", () => client.orgs.projects.builds.getGitInfo({
1833
+ orgId: config.thirdEyeOrgId,
1834
+ projectId: config.thirdEyeProjectId,
1835
+ branch
1836
+ }));
1837
+ };
1838
+ const sendFindSquashMergeParentsToAPI = async (config, apiToken, commits) => {
1839
+ const client = createClient(config.thirdEyePlatform, void 0, apiToken);
1840
+ return (await withRetry("findSquashMergeParents", () => client.orgs.projects.builds.findSquashMergeParents({
1841
+ orgId: config.thirdEyeOrgId,
1842
+ projectId: config.thirdEyeProjectId,
1843
+ commits
1844
+ }))).parents;
1845
+ };
1799
1846
  const sendFinalizeToAPI = async (config, apiToken) => {
1800
1847
  const client = createClient(config.thirdEyePlatform, void 0, apiToken);
1801
1848
  return withRetry("finalize", () => client.orgs.projects.builds.finalize({
@@ -1918,7 +1965,8 @@ const uploadRequiredShots = async ({ config, apiToken, uploadToken, requiredFile
1918
1965
  ...shotItem.viewport ? { viewport: shotItem.viewport } : {},
1919
1966
  ...shotItem.breakpoint !== void 0 ? { breakpoint: shotItem.breakpoint } : {},
1920
1967
  ...domHtml ? { dom_html: domHtml } : {},
1921
- ...dependencyMap && shotItem.importPath && dependencyMap.has(shotItem.importPath) ? { dependencies: dependencyMap.get(shotItem.importPath) } : {}
1968
+ ...dependencyMap && shotItem.importPath && dependencyMap.has(shotItem.importPath) ? { dependencies: dependencyMap.get(shotItem.importPath) } : {},
1969
+ ...shotItem.designUrl ? { designUrl: shotItem.designUrl } : {}
1922
1970
  },
1923
1971
  logger
1924
1972
  });
@@ -2195,8 +2243,34 @@ const platformRunner = async (config, apiToken) => {
2195
2243
  `commitRefName = ${config.commitRefName}`,
2196
2244
  `commitHash = ${config.commitHash}`
2197
2245
  ].join("\n - "));
2246
+ log.process("info", "general", "🔍 Querying server for git info...");
2247
+ const gitInfo = await getGitInfoFromAPI(config, apiToken, config.commitRefName);
2248
+ const initialCommitsWithBuilds = [];
2249
+ if (gitInfo.lastBuildOnBranch) {
2250
+ const lastCommit = gitInfo.lastBuildOnBranch.commit;
2251
+ if (commitExists(lastCommit)) {
2252
+ log.process("info", "general", `📌 Seeding with last build on branch: ${lastCommit.slice(0, 7)}`);
2253
+ initialCommitsWithBuilds.push(lastCommit);
2254
+ } else log.process("info", "general", `📌 Last build on branch ${lastCommit.slice(0, 7)} not in local git (rebase?), skipping`);
2255
+ }
2198
2256
  log.process("info", "general", "🔍 Resolving ancestor builds from git history...");
2199
- await sendInitToAPI(config, apiToken, await getParentCommits((commits) => sendHasBuildsWithCommitsToAPI(config, apiToken, commits)));
2257
+ const { parentCommits, visitedCommitsWithoutBuilds } = await getParentCommits((commits) => sendHasBuildsWithCommitsToAPI(config, apiToken, commits), {
2258
+ firstCommittedAtSeconds: gitInfo.firstBuildCreatedAt ?? void 0,
2259
+ initialCommitsWithBuilds: initialCommitsWithBuilds.length > 0 ? initialCommitsWithBuilds : void 0
2260
+ });
2261
+ if (visitedCommitsWithoutBuilds.length > 0) {
2262
+ log.process("info", "general", `🔀 Checking ${Math.min(visitedCommitsWithoutBuilds.length, 100)} commits for squash merges...`);
2263
+ try {
2264
+ const squashResult = await sendFindSquashMergeParentsToAPI(config, apiToken, visitedCommitsWithoutBuilds.slice(0, 100));
2265
+ for (const { buildCommit } of squashResult) if (commitExists(buildCommit) && !parentCommits.includes(buildCommit)) {
2266
+ log.process("info", "general", `🔀 Found squash merge parent: ${buildCommit.slice(0, 7)}`);
2267
+ parentCommits.push(buildCommit);
2268
+ }
2269
+ } catch (err) {
2270
+ log.process("info", "general", `Squash merge detection failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
2271
+ }
2272
+ }
2273
+ await sendInitToAPI(config, apiToken, parentCommits, config.baseBranch ? getMergeBaseInfo(config.baseBranch) : null);
2200
2274
  if (!await checkForCachedBuild(config, apiToken)) {
2201
2275
  log.process("info", "general", "📂 Creating shot folders");
2202
2276
  const createShotsStart = process.hrtime();