@gfxlabs/third-eye-cli 3.24.3 → 3.25.1
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 +242 -27
- package/dist/bin.mjs.map +1 -1
- package/package.json +1 -1
package/dist/bin.mjs
CHANGED
|
@@ -27,6 +27,16 @@ import { RPCLink } from "@orpc/client/fetch";
|
|
|
27
27
|
import { execa } from "execa";
|
|
28
28
|
import { XMLParser } from "fast-xml-parser";
|
|
29
29
|
//#region \0rolldown/runtime.js
|
|
30
|
+
var __defProp = Object.defineProperty;
|
|
31
|
+
var __exportAll = (all, no_symbols) => {
|
|
32
|
+
let target = {};
|
|
33
|
+
for (var name in all) __defProp(target, name, {
|
|
34
|
+
get: all[name],
|
|
35
|
+
enumerable: true
|
|
36
|
+
});
|
|
37
|
+
if (!no_symbols) __defProp(target, Symbol.toStringTag, { value: "Module" });
|
|
38
|
+
return target;
|
|
39
|
+
};
|
|
30
40
|
var __require = /* @__PURE__ */ createRequire(import.meta.url);
|
|
31
41
|
//#endregion
|
|
32
42
|
//#region src/log.ts
|
|
@@ -184,7 +194,8 @@ z$1.object({
|
|
|
184
194
|
storyName: z$1.string().optional(),
|
|
185
195
|
storyId: z$1.string().optional(),
|
|
186
196
|
storyArgs: z$1.record(z$1.string(), z$1.unknown()).optional(),
|
|
187
|
-
tags: z$1.array(z$1.string()).optional()
|
|
197
|
+
tags: z$1.array(z$1.string()).optional(),
|
|
198
|
+
designUrl: z$1.string().optional()
|
|
188
199
|
});
|
|
189
200
|
//#endregion
|
|
190
201
|
//#region src/config.ts
|
|
@@ -1035,7 +1046,8 @@ const generateStorybookShotItems = (baseUrl, stories, mask, modeBreakpoints, bro
|
|
|
1035
1046
|
componentPath: story.kind,
|
|
1036
1047
|
storyName: story.story,
|
|
1037
1048
|
storyId: story.id,
|
|
1038
|
-
storyArgs: story.parameters?.thirdeye?.args
|
|
1049
|
+
storyArgs: story.parameters?.thirdeye?.args,
|
|
1050
|
+
designUrl: (story.parameters?.design)?.url
|
|
1039
1051
|
};
|
|
1040
1052
|
const storyLevelBreakpoints = story.parameters?.thirdeye?.breakpoints ?? [];
|
|
1041
1053
|
const breakpoints = selectBreakpoints(config.breakpoints, modeBreakpoints, storyLevelBreakpoints);
|
|
@@ -1229,6 +1241,22 @@ const getPagesFromExternalLoader = async () => {
|
|
|
1229
1241
|
};
|
|
1230
1242
|
//#endregion
|
|
1231
1243
|
//#region src/shots/shots.ts
|
|
1244
|
+
/**
|
|
1245
|
+
* Wait for Storybook play functions to complete before screenshotting.
|
|
1246
|
+
* Checks __STORYBOOK_PREVIEW__.currentRender.phase and data-play-fn-done attribute.
|
|
1247
|
+
*/
|
|
1248
|
+
const waitForPlayFunction = async (page, shotItem, logger) => {
|
|
1249
|
+
if (shotItem.shotMode !== "storybook") return;
|
|
1250
|
+
try {
|
|
1251
|
+
await page.waitForFunction(() => {
|
|
1252
|
+
if (window.__STORYBOOK_PREVIEW__?.currentRender?.phase === "playing") return false;
|
|
1253
|
+
if (document.getElementById("storybook-root")?.dataset.playFnDone === "false") return false;
|
|
1254
|
+
return true;
|
|
1255
|
+
}, { timeout: config.timeouts?.loadState ?? 3e4 });
|
|
1256
|
+
} catch {
|
|
1257
|
+
logger.process("info", "general", `Play function wait timed out for ${shotItem.id} — proceeding with screenshot`);
|
|
1258
|
+
}
|
|
1259
|
+
};
|
|
1232
1260
|
const takeScreenShot = async ({ browser, shotItem, logger }) => {
|
|
1233
1261
|
const context = await browser.newContext(shotItem.browserConfig);
|
|
1234
1262
|
const page = await context.newPage();
|
|
@@ -1273,6 +1301,7 @@ const takeScreenShot = async ({ browser, shotItem, logger }) => {
|
|
|
1273
1301
|
} catch (error) {
|
|
1274
1302
|
logger.process("error", "timeout", `Timeout while waiting for all network requests: ${shotItem.url}`, error);
|
|
1275
1303
|
}
|
|
1304
|
+
await waitForPlayFunction(page, shotItem, logger);
|
|
1276
1305
|
if (config.beforeScreenshot) await config.beforeScreenshot(page, {
|
|
1277
1306
|
shotMode: shotItem.shotMode,
|
|
1278
1307
|
id: shotItem.id,
|
|
@@ -1640,6 +1669,12 @@ const createShots = async (turboSnapFilter) => {
|
|
|
1640
1669
|
//#region src/git.ts
|
|
1641
1670
|
const INITIAL_BATCH_SIZE = 20;
|
|
1642
1671
|
/**
|
|
1672
|
+
* Check if a commit exists in the local git index.
|
|
1673
|
+
*/
|
|
1674
|
+
const commitExists = (commit) => {
|
|
1675
|
+
return execGit(`git cat-file -t ${commit}`) === "commit";
|
|
1676
|
+
};
|
|
1677
|
+
/**
|
|
1643
1678
|
* Execute a git command and return the trimmed output.
|
|
1644
1679
|
*/
|
|
1645
1680
|
const execGit = (command) => {
|
|
@@ -1652,34 +1687,28 @@ const execGit = (command) => {
|
|
|
1652
1687
|
return "";
|
|
1653
1688
|
}
|
|
1654
1689
|
};
|
|
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) => {
|
|
1690
|
+
const getParentCommits = async (hasBuildsWithCommits, options) => {
|
|
1669
1691
|
if (!execGit("git rev-parse HEAD")) {
|
|
1670
1692
|
log.process("info", "general", "Not a git repository, skipping ancestor detection");
|
|
1671
|
-
return
|
|
1693
|
+
return {
|
|
1694
|
+
parentCommits: [],
|
|
1695
|
+
visitedCommitsWithoutBuilds: []
|
|
1696
|
+
};
|
|
1672
1697
|
}
|
|
1673
1698
|
if (!execGit("git --no-pager log -n 1 --skip=1 --format=\"%H\"")) {
|
|
1674
1699
|
log.process("info", "general", "Initial commit, no ancestors");
|
|
1675
|
-
return
|
|
1700
|
+
return {
|
|
1701
|
+
parentCommits: [],
|
|
1702
|
+
visitedCommitsWithoutBuilds: []
|
|
1703
|
+
};
|
|
1676
1704
|
}
|
|
1677
|
-
|
|
1705
|
+
const sinceArg = options?.firstCommittedAtSeconds ? `--since ${options.firstCommittedAtSeconds}` : "";
|
|
1706
|
+
let commitsWithBuilds = [...options?.initialCommitsWithBuilds ?? []];
|
|
1678
1707
|
let commitsWithoutBuilds = [];
|
|
1679
1708
|
let limit = INITIAL_BATCH_SIZE;
|
|
1680
1709
|
for (;;) {
|
|
1681
1710
|
const notArgs = commitsWithBuilds.map((c) => c.trim()).join(" ");
|
|
1682
|
-
const output = execGit(`git rev-list HEAD -n ${limit + commitsWithoutBuilds.length}${notArgs ? ` --not ${notArgs}` : ""}`);
|
|
1711
|
+
const output = execGit(`git rev-list HEAD ${sinceArg} -n ${limit + commitsWithoutBuilds.length}${notArgs ? ` --not ${notArgs}` : ""}`);
|
|
1683
1712
|
const candidates = (output ? output.split("\n").filter(Boolean) : []).filter((c) => !commitsWithBuilds.includes(c)).filter((c) => !commitsWithoutBuilds.includes(c)).slice(0, limit);
|
|
1684
1713
|
if (candidates.length === 0) break;
|
|
1685
1714
|
log.process("info", "general", `🔍 Checking ${candidates.length} commits for builds (batch size ${limit})`);
|
|
@@ -1695,7 +1724,10 @@ const getParentCommits = async (hasBuildsWithCommits) => {
|
|
|
1695
1724
|
}
|
|
1696
1725
|
if (commitsWithBuilds.length === 0) {
|
|
1697
1726
|
log.process("info", "general", "No ancestor builds found — this may be the first build");
|
|
1698
|
-
return
|
|
1727
|
+
return {
|
|
1728
|
+
parentCommits: [],
|
|
1729
|
+
visitedCommitsWithoutBuilds: commitsWithoutBuilds
|
|
1730
|
+
};
|
|
1699
1731
|
}
|
|
1700
1732
|
if (commitsWithBuilds.length > 1) {
|
|
1701
1733
|
const parentArgs = commitsWithBuilds.map((c) => `"${c}^@"`).join(" ");
|
|
@@ -1704,7 +1736,31 @@ const getParentCommits = async (hasBuildsWithCommits) => {
|
|
|
1704
1736
|
if (maxCommits.length > 0) commitsWithBuilds = maxCommits;
|
|
1705
1737
|
}
|
|
1706
1738
|
log.process("info", "general", `📌 Found ${commitsWithBuilds.length} ancestor build(s): ${commitsWithBuilds.map((c) => c.slice(0, 7)).join(", ")}`);
|
|
1707
|
-
return
|
|
1739
|
+
return {
|
|
1740
|
+
parentCommits: commitsWithBuilds,
|
|
1741
|
+
visitedCommitsWithoutBuilds: commitsWithoutBuilds
|
|
1742
|
+
};
|
|
1743
|
+
};
|
|
1744
|
+
/**
|
|
1745
|
+
* Compute the merge base commit between the current HEAD and a base branch,
|
|
1746
|
+
* plus a list of ancestor commits on the base branch from that point backwards.
|
|
1747
|
+
* This gives the server enough git history to walk back and find a build
|
|
1748
|
+
* even if the exact merge base commit doesn't have one.
|
|
1749
|
+
*/
|
|
1750
|
+
const getMergeBaseInfo = (baseBranch) => {
|
|
1751
|
+
const mergeBaseCommit = execGit(`git merge-base HEAD ${execGit(`git rev-parse origin/${baseBranch}`) ? `origin/${baseBranch}` : baseBranch}`);
|
|
1752
|
+
if (!mergeBaseCommit) {
|
|
1753
|
+
log.process("info", "general", `Could not compute merge base with ${baseBranch}`);
|
|
1754
|
+
return null;
|
|
1755
|
+
}
|
|
1756
|
+
log.process("info", "general", `📍 Merge base with ${baseBranch}: ${mergeBaseCommit.slice(0, 7)}`);
|
|
1757
|
+
const ancestorsOutput = execGit(`git rev-list ${mergeBaseCommit} -n 50`);
|
|
1758
|
+
const mergeBaseAncestors = ancestorsOutput ? ancestorsOutput.split("\n").filter(Boolean) : [];
|
|
1759
|
+
log.process("info", "general", `📍 Got ${mergeBaseAncestors.length} merge base ancestors for build lookup`);
|
|
1760
|
+
return {
|
|
1761
|
+
mergeBaseCommit,
|
|
1762
|
+
mergeBaseAncestors
|
|
1763
|
+
};
|
|
1708
1764
|
};
|
|
1709
1765
|
//#endregion
|
|
1710
1766
|
//#region ../shared/dist/client.js
|
|
@@ -1744,6 +1800,21 @@ const createTypedClient = (options) => {
|
|
|
1744
1800
|
};
|
|
1745
1801
|
//#endregion
|
|
1746
1802
|
//#region src/api.ts
|
|
1803
|
+
var api_exports = /* @__PURE__ */ __exportAll({
|
|
1804
|
+
getAffectedStories: () => getAffectedStories,
|
|
1805
|
+
getApiToken: () => getApiToken,
|
|
1806
|
+
getGitInfoFromAPI: () => getGitInfoFromAPI,
|
|
1807
|
+
prepareUpload: () => prepareUpload,
|
|
1808
|
+
processShots: () => processShots,
|
|
1809
|
+
sendCheckCacheToAPI: () => sendCheckCacheToAPI,
|
|
1810
|
+
sendFinalizeToAPI: () => sendFinalizeToAPI,
|
|
1811
|
+
sendFindSquashMergeParentsToAPI: () => sendFindSquashMergeParentsToAPI,
|
|
1812
|
+
sendHasBuildsWithCommitsToAPI: () => sendHasBuildsWithCommitsToAPI,
|
|
1813
|
+
sendInitToAPI: () => sendInitToAPI,
|
|
1814
|
+
sendRecordLogsToAPI: () => sendRecordLogsToAPI,
|
|
1815
|
+
uploadShot: () => uploadShot,
|
|
1816
|
+
uploadStorybookArchive: () => uploadStorybookArchive
|
|
1817
|
+
});
|
|
1747
1818
|
const createClient = (platformUrl, apiKey, apiToken) => createTypedClient({
|
|
1748
1819
|
url: platformUrl,
|
|
1749
1820
|
apiKey,
|
|
@@ -1775,7 +1846,7 @@ const getApiToken = async (config) => {
|
|
|
1775
1846
|
process.exit(1);
|
|
1776
1847
|
}
|
|
1777
1848
|
};
|
|
1778
|
-
const sendInitToAPI = async (config, apiToken, parentCommits) => {
|
|
1849
|
+
const sendInitToAPI = async (config, apiToken, parentCommits, mergeBaseInfo) => {
|
|
1779
1850
|
const client = createClient(config.thirdEyePlatform, void 0, apiToken);
|
|
1780
1851
|
return withRetry("init", () => client.orgs.projects.builds.init({
|
|
1781
1852
|
orgId: config.thirdEyeOrgId,
|
|
@@ -1785,7 +1856,9 @@ const sendInitToAPI = async (config, apiToken, parentCommits) => {
|
|
|
1785
1856
|
buildNumber: config.ciBuildNumber,
|
|
1786
1857
|
baseBranch: config.baseBranch || void 0,
|
|
1787
1858
|
prNumber: config.prNumber,
|
|
1788
|
-
parentCommits
|
|
1859
|
+
parentCommits,
|
|
1860
|
+
mergeBaseCommit: mergeBaseInfo?.mergeBaseCommit,
|
|
1861
|
+
mergeBaseAncestors: mergeBaseInfo?.mergeBaseAncestors
|
|
1789
1862
|
}));
|
|
1790
1863
|
};
|
|
1791
1864
|
const sendHasBuildsWithCommitsToAPI = async (config, apiToken, commits) => {
|
|
@@ -1796,6 +1869,22 @@ const sendHasBuildsWithCommitsToAPI = async (config, apiToken, commits) => {
|
|
|
1796
1869
|
commits
|
|
1797
1870
|
}))).commits;
|
|
1798
1871
|
};
|
|
1872
|
+
const getGitInfoFromAPI = async (config, apiToken, branch) => {
|
|
1873
|
+
const client = createClient(config.thirdEyePlatform, void 0, apiToken);
|
|
1874
|
+
return withRetry("getGitInfo", () => client.orgs.projects.builds.getGitInfo({
|
|
1875
|
+
orgId: config.thirdEyeOrgId,
|
|
1876
|
+
projectId: config.thirdEyeProjectId,
|
|
1877
|
+
branch
|
|
1878
|
+
}));
|
|
1879
|
+
};
|
|
1880
|
+
const sendFindSquashMergeParentsToAPI = async (config, apiToken, commits) => {
|
|
1881
|
+
const client = createClient(config.thirdEyePlatform, void 0, apiToken);
|
|
1882
|
+
return (await withRetry("findSquashMergeParents", () => client.orgs.projects.builds.findSquashMergeParents({
|
|
1883
|
+
orgId: config.thirdEyeOrgId,
|
|
1884
|
+
projectId: config.thirdEyeProjectId,
|
|
1885
|
+
commits
|
|
1886
|
+
}))).parents;
|
|
1887
|
+
};
|
|
1799
1888
|
const sendFinalizeToAPI = async (config, apiToken) => {
|
|
1800
1889
|
const client = createClient(config.thirdEyePlatform, void 0, apiToken);
|
|
1801
1890
|
return withRetry("finalize", () => client.orgs.projects.builds.finalize({
|
|
@@ -1918,7 +2007,8 @@ const uploadRequiredShots = async ({ config, apiToken, uploadToken, requiredFile
|
|
|
1918
2007
|
...shotItem.viewport ? { viewport: shotItem.viewport } : {},
|
|
1919
2008
|
...shotItem.breakpoint !== void 0 ? { breakpoint: shotItem.breakpoint } : {},
|
|
1920
2009
|
...domHtml ? { dom_html: domHtml } : {},
|
|
1921
|
-
...dependencyMap && shotItem.importPath && dependencyMap.has(shotItem.importPath) ? { dependencies: dependencyMap.get(shotItem.importPath) } : {}
|
|
2010
|
+
...dependencyMap && shotItem.importPath && dependencyMap.has(shotItem.importPath) ? { dependencies: dependencyMap.get(shotItem.importPath) } : {},
|
|
2011
|
+
...shotItem.designUrl ? { designUrl: shotItem.designUrl } : {}
|
|
1922
2012
|
},
|
|
1923
2013
|
logger
|
|
1924
2014
|
});
|
|
@@ -2195,8 +2285,44 @@ const platformRunner = async (config, apiToken) => {
|
|
|
2195
2285
|
`commitRefName = ${config.commitRefName}`,
|
|
2196
2286
|
`commitHash = ${config.commitHash}`
|
|
2197
2287
|
].join("\n - "));
|
|
2288
|
+
log.process("info", "general", "🔍 Querying server for git info...");
|
|
2289
|
+
const gitInfo = await getGitInfoFromAPI(config, apiToken, config.commitRefName);
|
|
2290
|
+
const initialCommitsWithBuilds = [];
|
|
2291
|
+
if (gitInfo.lastBuildOnBranch) {
|
|
2292
|
+
const lastCommit = gitInfo.lastBuildOnBranch.commit;
|
|
2293
|
+
if (commitExists(lastCommit)) {
|
|
2294
|
+
log.process("info", "general", `📌 Seeding with last build on branch: ${lastCommit.slice(0, 7)}`);
|
|
2295
|
+
initialCommitsWithBuilds.push(lastCommit);
|
|
2296
|
+
} else log.process("info", "general", `📌 Last build on branch ${lastCommit.slice(0, 7)} not in local git (rebase?), skipping`);
|
|
2297
|
+
}
|
|
2298
|
+
if (config.baseBranch) {
|
|
2299
|
+
const mergeBase = getMergeBaseInfo(config.baseBranch);
|
|
2300
|
+
if (mergeBase) {
|
|
2301
|
+
const mbCommitsWithBuilds = await sendHasBuildsWithCommitsToAPI(config, apiToken, mergeBase.mergeBaseAncestors);
|
|
2302
|
+
for (const c of mbCommitsWithBuilds) if (!initialCommitsWithBuilds.includes(c)) {
|
|
2303
|
+
log.process("info", "general", `📌 Seeding with base branch build: ${c.slice(0, 7)}`);
|
|
2304
|
+
initialCommitsWithBuilds.push(c);
|
|
2305
|
+
}
|
|
2306
|
+
}
|
|
2307
|
+
}
|
|
2198
2308
|
log.process("info", "general", "🔍 Resolving ancestor builds from git history...");
|
|
2199
|
-
|
|
2309
|
+
const { parentCommits, visitedCommitsWithoutBuilds } = await getParentCommits((commits) => sendHasBuildsWithCommitsToAPI(config, apiToken, commits), {
|
|
2310
|
+
firstCommittedAtSeconds: gitInfo.firstBuildCreatedAt ?? void 0,
|
|
2311
|
+
initialCommitsWithBuilds: initialCommitsWithBuilds.length > 0 ? initialCommitsWithBuilds : void 0
|
|
2312
|
+
});
|
|
2313
|
+
if (visitedCommitsWithoutBuilds.length > 0) {
|
|
2314
|
+
log.process("info", "general", `🔀 Checking ${Math.min(visitedCommitsWithoutBuilds.length, 100)} commits for squash merges...`);
|
|
2315
|
+
try {
|
|
2316
|
+
const squashResult = await sendFindSquashMergeParentsToAPI(config, apiToken, visitedCommitsWithoutBuilds.slice(0, 100));
|
|
2317
|
+
for (const { buildCommit } of squashResult) if (commitExists(buildCommit) && !parentCommits.includes(buildCommit)) {
|
|
2318
|
+
log.process("info", "general", `🔀 Found squash merge parent: ${buildCommit.slice(0, 7)}`);
|
|
2319
|
+
parentCommits.push(buildCommit);
|
|
2320
|
+
}
|
|
2321
|
+
} catch (err) {
|
|
2322
|
+
log.process("info", "general", `Squash merge detection failed (non-fatal): ${err instanceof Error ? err.message : String(err)}`);
|
|
2323
|
+
}
|
|
2324
|
+
}
|
|
2325
|
+
await sendInitToAPI(config, apiToken, parentCommits, config.baseBranch ? getMergeBaseInfo(config.baseBranch) : null);
|
|
2200
2326
|
if (!await checkForCachedBuild(config, apiToken)) {
|
|
2201
2327
|
log.process("info", "general", "📂 Creating shot folders");
|
|
2202
2328
|
const createShotsStart = process.hrtime();
|
|
@@ -2298,6 +2424,90 @@ const platformRunner = async (config, apiToken) => {
|
|
|
2298
2424
|
process.exit(1);
|
|
2299
2425
|
}
|
|
2300
2426
|
};
|
|
2427
|
+
const patchBuildRunner = async (patchBuildArg, config, apiToken) => {
|
|
2428
|
+
const { execSync } = await import("node:child_process");
|
|
2429
|
+
const parts = patchBuildArg.split("...");
|
|
2430
|
+
if (parts.length !== 2) {
|
|
2431
|
+
log.process("error", "general", `Invalid --patch-build format. Expected: head...base (e.g. feature...main)`);
|
|
2432
|
+
process.exit(1);
|
|
2433
|
+
}
|
|
2434
|
+
const [headRef, baseRef] = parts;
|
|
2435
|
+
log.process("info", "general", `🩹 Patch build: computing merge base between ${headRef} and ${baseRef}...`);
|
|
2436
|
+
let mergeBaseCommit;
|
|
2437
|
+
try {
|
|
2438
|
+
mergeBaseCommit = execSync(`git merge-base ${headRef} ${baseRef}`, { encoding: "utf-8" }).trim();
|
|
2439
|
+
} catch {
|
|
2440
|
+
log.process("error", "general", `Failed to compute merge base between ${headRef} and ${baseRef}`);
|
|
2441
|
+
process.exit(1);
|
|
2442
|
+
}
|
|
2443
|
+
log.process("info", "general", `📍 Merge base commit: ${mergeBaseCommit.slice(0, 7)}`);
|
|
2444
|
+
const { sendHasBuildsWithCommitsToAPI: checkCommits } = await Promise.resolve().then(() => api_exports);
|
|
2445
|
+
if ((await checkCommits(config, apiToken, [mergeBaseCommit])).length > 0) {
|
|
2446
|
+
log.process("info", "general", `✅ Build already exists for merge base ${mergeBaseCommit.slice(0, 7)}, no patch build needed.`);
|
|
2447
|
+
return;
|
|
2448
|
+
}
|
|
2449
|
+
const originalRef = execSync("git rev-parse HEAD", { encoding: "utf-8" }).trim();
|
|
2450
|
+
const originalBranch = execSync("git rev-parse --abbrev-ref HEAD", { encoding: "utf-8" }).trim();
|
|
2451
|
+
log.process("info", "general", `📦 Saving current state (${originalBranch} @ ${originalRef.slice(0, 7)})...`);
|
|
2452
|
+
let hasStash = false;
|
|
2453
|
+
try {
|
|
2454
|
+
hasStash = !execSync("git stash", { encoding: "utf-8" }).trim().includes("No local changes");
|
|
2455
|
+
} catch {}
|
|
2456
|
+
try {
|
|
2457
|
+
log.process("info", "general", `🔄 Checking out merge base ${mergeBaseCommit.slice(0, 7)}...`);
|
|
2458
|
+
execSync(`git checkout ${mergeBaseCommit}`, { stdio: "pipe" });
|
|
2459
|
+
log.process("info", "general", "📦 Installing dependencies...");
|
|
2460
|
+
try {
|
|
2461
|
+
execSync("pnpm install --frozen-lockfile", {
|
|
2462
|
+
stdio: "pipe",
|
|
2463
|
+
timeout: 12e4
|
|
2464
|
+
});
|
|
2465
|
+
} catch {
|
|
2466
|
+
try {
|
|
2467
|
+
execSync("npm ci", {
|
|
2468
|
+
stdio: "pipe",
|
|
2469
|
+
timeout: 12e4
|
|
2470
|
+
});
|
|
2471
|
+
} catch {
|
|
2472
|
+
execSync("npm install", {
|
|
2473
|
+
stdio: "pipe",
|
|
2474
|
+
timeout: 12e4
|
|
2475
|
+
});
|
|
2476
|
+
}
|
|
2477
|
+
}
|
|
2478
|
+
log.process("info", "general", "📖 Building Storybook...");
|
|
2479
|
+
if (config.storybookShots?.storybookUrl) try {
|
|
2480
|
+
execSync("npx storybook build", {
|
|
2481
|
+
stdio: "pipe",
|
|
2482
|
+
timeout: 3e5
|
|
2483
|
+
});
|
|
2484
|
+
} catch {
|
|
2485
|
+
log.process("info", "general", "Storybook build command failed, trying build-storybook...");
|
|
2486
|
+
execSync("npx build-storybook", {
|
|
2487
|
+
stdio: "pipe",
|
|
2488
|
+
timeout: 3e5
|
|
2489
|
+
});
|
|
2490
|
+
}
|
|
2491
|
+
const originalCommitHash = config.commitHash;
|
|
2492
|
+
const originalRefName = config.commitRefName;
|
|
2493
|
+
config.commitHash = mergeBaseCommit;
|
|
2494
|
+
config.commitRefName = baseRef;
|
|
2495
|
+
log.process("info", "general", "🚀 Running Third Eye for merge base...");
|
|
2496
|
+
await platformRunner(config, apiToken);
|
|
2497
|
+
config.commitHash = originalCommitHash;
|
|
2498
|
+
config.commitRefName = originalRefName;
|
|
2499
|
+
log.process("info", "general", `✅ Patch build complete for merge base ${mergeBaseCommit.slice(0, 7)}`);
|
|
2500
|
+
} finally {
|
|
2501
|
+
log.process("info", "general", `🔄 Restoring original state...`);
|
|
2502
|
+
try {
|
|
2503
|
+
if (originalBranch === "HEAD") execSync(`git checkout ${originalRef}`, { stdio: "pipe" });
|
|
2504
|
+
else execSync(`git checkout ${originalBranch}`, { stdio: "pipe" });
|
|
2505
|
+
if (hasStash) execSync("git stash pop", { stdio: "pipe" });
|
|
2506
|
+
} catch (err) {
|
|
2507
|
+
log.process("error", "general", `Failed to restore git state: ${err instanceof Error ? err.message : String(err)}`);
|
|
2508
|
+
}
|
|
2509
|
+
}
|
|
2510
|
+
};
|
|
2301
2511
|
//#endregion
|
|
2302
2512
|
//#region src/docker-runner/utils.ts
|
|
2303
2513
|
const executeDockerRun = async ({ version }) => {
|
|
@@ -2372,7 +2582,11 @@ const generatePagesFromSitemap = async () => {
|
|
|
2372
2582
|
//#endregion
|
|
2373
2583
|
//#region src/bin.ts
|
|
2374
2584
|
const __dirname = path.dirname(fileURLToPath(import.meta.url));
|
|
2375
|
-
const
|
|
2585
|
+
const args = yargs(hideBin(process.argv)).option("patch-build", {
|
|
2586
|
+
type: "string",
|
|
2587
|
+
describe: "Create a patch build for the merge base. Format: head...base (e.g. feature...main)"
|
|
2588
|
+
}).parseSync();
|
|
2589
|
+
const commandArgs = args._;
|
|
2376
2590
|
const version = getVersion();
|
|
2377
2591
|
if (version) log.process("info", "general", `Version: ${version}`);
|
|
2378
2592
|
(async () => {
|
|
@@ -2401,6 +2615,7 @@ if (version) log.process("info", "general", `Version: ${version}`);
|
|
|
2401
2615
|
log.process("info", "general", `🚀 Starting Lost Pixel in 'platform' mode`);
|
|
2402
2616
|
const apiToken = await getPlatformApiToken(config);
|
|
2403
2617
|
if (commandArgs.includes("finalize")) await sendFinalizeToAPI(config, apiToken);
|
|
2618
|
+
else if (args["patch-build"]) await patchBuildRunner(args["patch-build"], config, apiToken);
|
|
2404
2619
|
else await platformRunner(config, apiToken);
|
|
2405
2620
|
} else {
|
|
2406
2621
|
log.process("info", "general", `🚀 Starting Lost Pixel in 'generateOnly' mode`);
|