From ad7d50c8acb8b97edcb5372ebd06f1f690386fa2 Mon Sep 17 00:00:00 2001 From: Claude Date: Mon, 27 Jul 2026 09:19:50 +0000 Subject: [PATCH] Verify and retry compiled asset copy to fix missing open-write bundle (#7) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit CI logs from two separate deploys showed os-app/open-write/__compiled consistently missing from the uploaded Pages artifact, while every other controller's __compiled output copied fine — even though the build step logs confirm it created the file, and local rebuilds always reproduce it correctly. open-write is the last (and heaviest, ~7s) module olsk-rollup builds; this looks like a race where the build CLI logs "created" and exits before that module's output is fully flushed to disk on the CI runner. Rather than chase the exact upstream timing bug, make the copy self-verifying: after copying os-app to .static, check that every module with a rollup-start.js has its __compiled output present in the copy, retrying (with a short wait) up to 5 times before failing the deploy loudly. Better to fail CI than silently ship a page missing its script and stylesheet, as happened in the last two deploys. --- static-export.js | 64 ++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 56 insertions(+), 8 deletions(-) diff --git a/static-export.js b/static-export.js index 6c08b7fb..af82b3c7 100644 --- a/static-export.js +++ b/static-export.js @@ -118,10 +118,61 @@ const OLSKStaticExportWaitForServer = async function () { throw new Error('Server did not become ready in time'); }; -const OLSKStaticExportCopyBuiltAssets = function () { - fs.cpSync(path.join(kRootDirectory, 'os-app'), kOutputDirectory, { - recursive: true, - }); +const OLSKStaticExportFindRollupModuleDirectories = function () { + const moduleDirectories = []; + + (function walk(directory) { + for (const entry of fs.readdirSync(directory, { withFileTypes: true })) { + if (entry.name.startsWith('__') || entry.name === 'node_modules') { + continue; + } + + const fullPath = path.join(directory, entry.name); + + if (entry.isDirectory()) { + walk(fullPath); + continue; + } + + if (entry.name === 'rollup-start.js') { + moduleDirectories.push(directory); + } + } + })(path.join(kRootDirectory, 'os-app')); + + return moduleDirectories; +}; + +const OLSKStaticExportCopyBuiltAssets = async function () { + // olsk-rollup has been observed to log "created ... __compiled/..." and + // exit for its last-built module before that module's output is fully + // flushed to disk, so a copy taken immediately after `npm run build` + // can silently miss it. Retry a few times before giving up loudly — + // better to fail the deploy than ship a page missing its script/style. + for (let attempt = 1; attempt <= 5; attempt++) { + fs.rmSync(kOutputDirectory, { recursive: true, force: true }); + fs.cpSync(path.join(kRootDirectory, 'os-app'), kOutputDirectory, { + recursive: true, + }); + + const missing = OLSKStaticExportFindRollupModuleDirectories().filter(function (moduleDirectory) { + const relativePath = path.relative(path.join(kRootDirectory, 'os-app'), moduleDirectory); + return !fs.existsSync(path.join(kOutputDirectory, relativePath, '__compiled')); + }); + + if (!missing.length) { + break; + } + + if (attempt === 5) { + throw new Error(`__compiled output missing after copy for: ${ missing.join(', ') }`); + } + + console.warn(`__compiled missing for ${ missing.join(', ') }, retrying copy (attempt ${ attempt })`); + await new Promise(function (resolve) { + setTimeout(resolve, 1000); + }); + } // Without this, GitHub Pages treats folders like `_shared` and // `__compiled` as Jekyll-reserved and silently excludes them from @@ -130,10 +181,7 @@ const OLSKStaticExportCopyBuiltAssets = function () { }; const OLSKStaticExportMain = async function () { - fs.rmSync(kOutputDirectory, { recursive: true, force: true }); - fs.mkdirSync(kOutputDirectory, { recursive: true }); - - OLSKStaticExportCopyBuiltAssets(); + await OLSKStaticExportCopyBuiltAssets(); const urls = Array.from(OLSKStaticExportDiscoverRoutes().reduce(function (coll, route) { OLSKStaticExportURLsForRoute(route).forEach(function (url) {