From 688a6ef4fd3d442c9e8cb07846748da652d5640e Mon Sep 17 00:00:00 2001 From: Peter Steinberger Date: Thu, 23 Apr 2026 03:11:42 +0100 Subject: [PATCH] ci: keep gateway watch skip-build artifact fresh --- scripts/check-gateway-watch-regression.mjs | 48 +++++++++++++++++-- .../check-gateway-watch-regression.test.ts | 26 +++++++++- 2 files changed, 70 insertions(+), 4 deletions(-) diff --git a/scripts/check-gateway-watch-regression.mjs b/scripts/check-gateway-watch-regression.mjs index 925ea5f9b16..3d42d52a8c9 100644 --- a/scripts/check-gateway-watch-regression.mjs +++ b/scripts/check-gateway-watch-regression.mjs @@ -95,6 +95,17 @@ function removePathIfExists(targetPath) { fs.rmSync(targetPath, { recursive: true, force: true }); } +function lstatIfExists(targetPath) { + try { + return fs.lstatSync(targetPath); + } catch (error) { + if (error?.code === "ENOENT") { + return null; + } + throw error; + } +} + function normalizePath(filePath) { return filePath.replaceAll("\\", "/"); } @@ -112,7 +123,15 @@ function listTreeEntries(rootName) { if (!current) { continue; } - const dirents = fs.readdirSync(current, { withFileTypes: true }); + let dirents; + try { + dirents = fs.readdirSync(current, { withFileTypes: true }); + } catch (error) { + if (error?.code === "ENOENT") { + continue; + } + throw error; + } for (const dirent of dirents) { const fullPath = path.join(current, dirent.name); const relativePath = normalizePath(path.relative(process.cwd(), fullPath)); @@ -159,7 +178,10 @@ function snapshotTree(rootName) { if (!current) { continue; } - const currentStats = fs.lstatSync(current); + const currentStats = lstatIfExists(current); + if (!currentStats) { + continue; + } stats.entries += 1; if (currentStats.isDirectory()) { stats.directories += 1; @@ -579,6 +601,14 @@ function buildRunNodeDeps(env) { }; } +export function shouldRefreshBuildStampForRestoredArtifacts(params) { + return ( + params.skipBuild === true && + params.buildRequirement?.shouldBuild === true && + params.buildRequirement.reason === "config_newer" + ); +} + async function main() { const options = parseArgs(process.argv.slice(2)); ensureDir(options.outputDir); @@ -594,7 +624,19 @@ async function main() { writeBuildStamp({ cwd: process.cwd() }); } - const preflightBuildRequirement = resolveBuildRequirement(buildRunNodeDeps(process.env)); + let preflightBuildRequirement = resolveBuildRequirement(buildRunNodeDeps(process.env)); + if ( + shouldRefreshBuildStampForRestoredArtifacts({ + skipBuild: options.skipBuild, + buildRequirement: preflightBuildRequirement, + }) + ) { + // CI's skip-build path restores a built dist artifact after checkout. + // Refresh the stamp so checkout mtimes for package/config files do not + // force a duplicate build during the bounded gateway:watch window. + writeBuildStamp({ cwd: process.cwd() }); + preflightBuildRequirement = resolveBuildRequirement(buildRunNodeDeps(process.env)); + } if ( preflightBuildRequirement.shouldBuild && preflightBuildRequirement.reason === "dirty_watched_tree" diff --git a/test/scripts/check-gateway-watch-regression.test.ts b/test/scripts/check-gateway-watch-regression.test.ts index 252713d061f..61d42c880b8 100644 --- a/test/scripts/check-gateway-watch-regression.test.ts +++ b/test/scripts/check-gateway-watch-regression.test.ts @@ -1,5 +1,8 @@ import { describe, expect, it } from "vitest"; -import { isIgnoredDistRuntimeWatchPath } from "../../scripts/check-gateway-watch-regression.mjs"; +import { + isIgnoredDistRuntimeWatchPath, + shouldRefreshBuildStampForRestoredArtifacts, +} from "../../scripts/check-gateway-watch-regression.mjs"; describe("check-gateway-watch-regression", () => { it("ignores top-level dist-runtime extension dependency repairs", () => { @@ -19,4 +22,25 @@ describe("check-gateway-watch-regression", () => { ), ).toBe(false); }); + + it("refreshes restored build stamps only for skip-build config mtime drift", () => { + expect( + shouldRefreshBuildStampForRestoredArtifacts({ + skipBuild: true, + buildRequirement: { shouldBuild: true, reason: "config_newer" }, + }), + ).toBe(true); + expect( + shouldRefreshBuildStampForRestoredArtifacts({ + skipBuild: false, + buildRequirement: { shouldBuild: true, reason: "config_newer" }, + }), + ).toBe(false); + expect( + shouldRefreshBuildStampForRestoredArtifacts({ + skipBuild: true, + buildRequirement: { shouldBuild: true, reason: "source_mtime_newer" }, + }), + ).toBe(false); + }); });