From 63de5254826250f557403b48b34b461b34ddadbc Mon Sep 17 00:00:00 2001 From: Forge Date: Tue, 22 Sep 2026 06:08:03 +0000 Subject: [PATCH] fix(marketplace): flush registry files with Windows-compatible handles --- .github/workflows/extensions-native.yml | 3 +++ scripts/validation/extensions-compatibility.cjs | 2 +- scripts/validation/https-marketplace.cjs | 2 +- scripts/validation/marketplace-browser.cjs | 3 ++- scripts/validation/marketplace-storage.cjs | 5 +++++ scripts/validation/native-validation.cjs | 4 ++++ src/plugins/marketplace-storage.ts | 7 ++++--- 7 files changed, 20 insertions(+), 6 deletions(-) diff --git a/.github/workflows/extensions-native.yml b/.github/workflows/extensions-native.yml index 2f51c53f1..d2805cb09 100644 --- a/.github/workflows/extensions-native.yml +++ b/.github/workflows/extensions-native.yml @@ -34,6 +34,9 @@ jobs: timeout-minutes: 35 env: CODEGRAPH_TELEMETRY: '0' + # This increment changes registry storage and hosted browser behavior. + # Prior core lifecycle/native-path/70-kill matrices remain recorded. + NATIVE_VALIDATION_SCOPE: marketplace steps: - uses: actions/checkout@v4 with: diff --git a/scripts/validation/extensions-compatibility.cjs b/scripts/validation/extensions-compatibility.cjs index 1c1c9e7a0..52d2f7338 100644 --- a/scripts/validation/extensions-compatibility.cjs +++ b/scripts/validation/extensions-compatibility.cjs @@ -168,4 +168,4 @@ async function cli(args, expected = 0) { await page?.screenshot({ path: path.join(out, 'failure.png'), fullPage: true }).catch(() => {}); fs.writeFileSync(path.join(out, 'failure.json'), JSON.stringify({ checks, error: String(error.stack).replace(/token=[a-f0-9]+/g, 'token=[redacted]') }, null, 2)); throw error; } finally { await browser?.close(); await https?.close(); await bridge?.close(); await server?.close(); fs.rmSync(lab, { recursive: true, force: true }); } -})().catch(error => { console.error(String(error.stack).replace(/token=[a-f0-9]+/g, 'token=[redacted]')); process.exitCode = 1; }); +})().catch(error => { console.error(String(error.stack).replace(/token=[a-f0-9]+/g, 'token=[redacted]')); if(error.cause) console.error(error.cause); process.exitCode = 1; }); diff --git a/scripts/validation/https-marketplace.cjs b/scripts/validation/https-marketplace.cjs index 7f4e78433..596df3f3c 100644 --- a/scripts/validation/https-marketplace.cjs +++ b/scripts/validation/https-marketplace.cjs @@ -20,7 +20,7 @@ exports.httpsMarketplace = async function(port, out) { try { const response = await fetch(url+'/api/health',{signal:AbortSignal.timeout(5000)}); if(response.ok && (await response.json()).ok) { fs.writeFileSync(path.join(out,'https-transport.json'),JSON.stringify({origin:url,kind:'temporary-public-tunnel',durableHosting:false,verifiedAt:new Date().toISOString()},null,2)); return { origin:url, close }; - }} catch { /* Wait for public route propagation. */ } + }} catch (error) { fs.appendFileSync(path.join(out,'https-network-errors.log'),new Date().toISOString()+' '+String(error)+' '+String(error.cause?.stack || '')+'\n'); } } await new Promise(resolve=>setTimeout(resolve,500)); } diff --git a/scripts/validation/marketplace-browser.cjs b/scripts/validation/marketplace-browser.cjs index 77a3caee5..e55ecc12f 100644 --- a/scripts/validation/marketplace-browser.cjs +++ b/scripts/validation/marketplace-browser.cjs @@ -173,6 +173,7 @@ function routes(root) { await popup.close(); await page.getByText('Reconnect CodeGraph', { exact: true }).waitFor(); checks.push('closed local companion expires the connection visibly'); + await page.setViewportSize({ width: 1440, height: 1000 }); await page.getByRole('link', { name: 'Publish an extension' }).click(); await page.locator('#artifact').setInputFiles({ name: 'example.cgext', mimeType: 'application/json', buffer: artifact('4.0.0') }); for (const [id, value] of Object.entries({ name: 'Browser example', publisher: 'Recovery publisher', description: 'Outage check', source: 'https://example.com/source', readme: 'Outage check' })) await page.locator('#'+id).fill(value); @@ -194,4 +195,4 @@ function routes(root) { if (attacker) await new Promise(resolve => attacker.close(resolve)); fs.rmSync(temp, { recursive: true, force: true }); } -})().catch(error => { console.error(safeError(error)); process.exitCode = 1; }); +})().catch(error => { console.error(safeError(error)); if(error.cause) console.error(error.cause); process.exitCode = 1; }); diff --git a/scripts/validation/marketplace-storage.cjs b/scripts/validation/marketplace-storage.cjs index 0a3e5214e..9bb26cde3 100644 --- a/scripts/validation/marketplace-storage.cjs +++ b/scripts/validation/marketplace-storage.cjs @@ -83,6 +83,11 @@ async function main() { assert.deepEqual(await (await fetch(restart.origin+'/api/extensions')).json(),catalog); assert.equal(storage.openMarketplaceVolume(volume).id,initialized.id); passed('fresh production process and different deployment cwd preserve catalog/identity/artifacts'); const backup = path.join(lab,'backup'); storage.backupMarketplaceVolume(volume,backup); + const pending = publish('in-flight-demo','1.0.0','before-commit'); await waitFor(()=>fs.existsSync(pending.ready)); + const concurrentBackup = path.join(lab,'concurrent-backup'); storage.backupMarketplaceVolume(volume,concurrentBackup); + pending.proc.kill('SIGKILL'); await pending.done; + assert.deepEqual(storage.verifyMarketplaceDatabase(path.join(concurrentBackup,'registry.sqlite')),before); + passed('backup concurrent with uncommitted publication captures only complete releases'); const restored = path.join(lab,'restored'); storage.restoreMarketplaceVolume(backup,restored); assert.deepEqual(storage.verifyMarketplaceDatabase(path.join(restored,'registry.sqlite')),before); const isolated = await server(restored,lab); diff --git a/scripts/validation/native-validation.cjs b/scripts/validation/native-validation.cjs index d8563a4c1..a58e43e69 100644 --- a/scripts/validation/native-validation.cjs +++ b/scripts/validation/native-validation.cjs @@ -8,6 +8,7 @@ const out = path.resolve(process.env.NATIVE_OUTPUT || `.qa/native/${process.plat fs.mkdirSync(out, { recursive: true }); const version = execFileSync('git', ['rev-parse', 'HEAD'], { cwd: root, encoding: 'utf8' }).trim(); const metadata = { revision: version, platform: process.platform, arch: process.arch, release: os.release(), + scope: process.env.NATIVE_VALIDATION_SCOPE || 'full', notRun: [], node: process.version, runnerOS: process.env.RUNNER_OS, runnerArch: process.env.RUNNER_ARCH, imageOS: process.env.ImageOS, imageVersion: process.env.ImageVersion, githubRun: process.env.GITHUB_RUN_ID, githubAttempt: process.env.GITHUB_RUN_ATTEMPT, @@ -20,6 +21,9 @@ const playwright = process.env.PLAYWRIGHT_MODULE || path.join(root, '.qa/browser env.PLAYWRIGHT_MODULE = playwright; env.CHROME_PATH = process.env.CHROME_PATH || require(playwright).chromium.executablePath(); async function run(name, command, args, timeout = 360000, overrides = {}) { + if (metadata.scope === 'marketplace' && !['build','marketplace-build','focused','registry-storage','external-author','compatibility','browser'].includes(name)) { + metadata.notRun.push({ name, reason: 'Unchanged core lifecycle; retain prior native evidence instead of rerunning' }); save(); return true; + } const step = { name, command: [command, ...args], cwd: root, revision: version, overrides, started: new Date().toISOString(), status: 'running' }; metadata.steps.push(step); save(); console.log('START', name); const logFile = path.join(out, name + '.log'); const log = fs.openSync(logFile, 'w'); diff --git a/src/plugins/marketplace-storage.ts b/src/plugins/marketplace-storage.ts index fc30d426e..7f7549c4c 100644 --- a/src/plugins/marketplace-storage.ts +++ b/src/plugins/marketplace-storage.ts @@ -8,11 +8,12 @@ import { parsePackage, sha256 } from './package'; const databaseName = 'registry.sqlite'; const markerName = 'registry-volume.json'; -function sync(file: string): void { - const fd = fs.openSync(file, 'r'); +function sync(file: string, directory = false): void { + // Windows FlushFileBuffers requires a writable file handle. + const fd = fs.openSync(file, directory ? 'r' : 'r+'); try { fs.fsyncSync(fd); } finally { fs.closeSync(fd); } } -function syncDir(directory: string): void { if (process.platform !== 'win32') sync(directory); } +function syncDir(directory: string): void { if (process.platform !== 'win32') sync(directory, true); } function writeNew(file: string, data: string): void { const fd = fs.openSync(file, 'wx', 0o600); try { fs.writeFileSync(fd, data); fs.fsyncSync(fd); } finally { fs.closeSync(fd); }