Skip to content

Commit f7352ae

Browse files
authored
Merge pull request #145 from beNative/codex/analyze-automatic-update-system-issues-0hx41n
Harden release manifest naming for Windows installers
2 parents 8f848da + 8224dd2 commit f7352ae

File tree

2 files changed

+269
-17
lines changed

2 files changed

+269
-17
lines changed

scripts/__tests__/release-workflow.test.mjs

Lines changed: 58 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -34,6 +34,7 @@ async function writeFixtureInstaller(directory, version, options = {}) {
3434
includeMetadata = true,
3535
additionalMetadata = [],
3636
assetSize = 1024,
37+
includeBlockmap = false,
3738
} = options;
3839

3940
const releaseDir = path.join(directory, 'release-artifacts', artifactDir, 'release');
@@ -43,6 +44,12 @@ async function writeFixtureInstaller(directory, version, options = {}) {
4344
const binary = crypto.randomBytes(assetSize);
4445
await fs.writeFile(installerPath, binary);
4546

47+
let blockmapPath = null;
48+
if (includeBlockmap) {
49+
blockmapPath = `${installerPath}.blockmap`;
50+
await fs.writeFile(blockmapPath, crypto.randomBytes(Math.max(256, Math.floor(assetSize / 4))));
51+
}
52+
4653
let metadataPath = null;
4754
if (includeMetadata && metadataFileName) {
4855
const metadata = {
@@ -84,6 +91,7 @@ async function writeFixtureInstaller(directory, version, options = {}) {
8491
artifactDir,
8592
assetPath: installerPath,
8693
assetName,
94+
blockmapPath,
8795
};
8896
}
8997

@@ -305,8 +313,14 @@ test('local verification can repair mismatched metadata when requested', async (
305313
test('metadata updates remain isolated across artifact directories with identical installer names', async (t) => {
306314
const workspace = await createTemporaryWorkspace(t);
307315
const version = '0.0.3';
308-
const x64 = await writeFixtureInstaller(workspace, version, { artifactDir: 'docforge-windows-x64' });
309-
const arm64 = await writeFixtureInstaller(workspace, version, { artifactDir: 'docforge-windows-arm64' });
316+
const x64 = await writeFixtureInstaller(workspace, version, {
317+
artifactDir: 'docforge-windows-x64',
318+
includeBlockmap: true,
319+
});
320+
const arm64 = await writeFixtureInstaller(workspace, version, {
321+
artifactDir: 'docforge-windows-arm64',
322+
includeBlockmap: true,
323+
});
310324

311325
const changelogPath = path.join(workspace, 'CHANGELOG.md');
312326
await fs.writeFile(
@@ -327,13 +341,16 @@ test('metadata updates remain isolated across artifact directories with identica
327341
filesOutputPath: manifestPath,
328342
});
329343

330-
const renamedInstallerName = `DocForge-Setup-${version}.exe`;
331-
const renamedX64 = path.join(x64.releaseDir, renamedInstallerName);
332-
const renamedArm64 = path.join(arm64.releaseDir, renamedInstallerName);
344+
const renamedX64Name = `DocForge-Setup-${version}.exe`;
345+
const renamedArm64Name = `DocForge-Setup-${version}-arm64.exe`;
346+
const renamedX64 = path.join(x64.releaseDir, renamedX64Name);
347+
const renamedArm64 = path.join(arm64.releaseDir, renamedArm64Name);
333348

334349
await Promise.all([
335350
assert.doesNotReject(() => fs.access(renamedX64)),
336351
assert.doesNotReject(() => fs.access(renamedArm64)),
352+
assert.doesNotReject(() => fs.access(path.join(x64.releaseDir, `${renamedX64Name}.blockmap`))),
353+
assert.doesNotReject(() => fs.access(path.join(arm64.releaseDir, `${renamedArm64Name}.blockmap`))),
337354
]);
338355

339356
const [x64Buffer, arm64Buffer] = await Promise.all([
@@ -358,20 +375,20 @@ test('metadata updates remain isolated across artifact directories with identica
358375
assert(entries.includes(expectedX64Entry), 'x64 installer should be listed in manifest');
359376
assert(entries.includes(expectedArm64Entry), 'arm64 installer should be listed in manifest');
360377

361-
const assertMetadataMatches = (metadata, expectedSha, bufferLength) => {
362-
assert.equal(metadata.path, renamedInstallerName);
378+
const assertMetadataMatches = (metadata, expectedName, expectedSha, bufferLength) => {
379+
assert.equal(metadata.path, expectedName);
363380
assert.equal(metadata.sha512, expectedSha);
364381
if (Object.prototype.hasOwnProperty.call(metadata, 'size')) {
365382
assert.equal(metadata.size, bufferLength);
366383
}
367384
assert(Array.isArray(metadata.files) && metadata.files.length === 1);
368-
assert.equal(metadata.files[0].url, renamedInstallerName);
385+
assert.equal(metadata.files[0].url, expectedName);
369386
assert.equal(metadata.files[0].sha512, expectedSha);
370387
assert.equal(metadata.files[0].size, bufferLength);
371388
};
372389

373-
assertMetadataMatches(x64Metadata, x64Sha, x64Buffer.length);
374-
assertMetadataMatches(arm64Metadata, arm64Sha, arm64Buffer.length);
390+
assertMetadataMatches(x64Metadata, renamedX64Name, x64Sha, x64Buffer.length);
391+
assertMetadataMatches(arm64Metadata, renamedArm64Name, arm64Sha, arm64Buffer.length);
375392

376393
await Promise.all([
377394
runLocalVerification(x64.releaseDir),
@@ -385,6 +402,7 @@ test('release workflow verifies metadata directories across platforms and publis
385402

386403
const ia32 = await writeFixtureInstaller(workspace, version, {
387404
artifactDir: 'docforge-windows-ia32',
405+
includeBlockmap: true,
388406
additionalMetadata: [
389407
{
390408
relativePath: path.join('win-ia32-unpacked', 'resources', 'app-update.yml'),
@@ -398,6 +416,7 @@ test('release workflow verifies metadata directories across platforms and publis
398416

399417
const x64 = await writeFixtureInstaller(workspace, version, {
400418
artifactDir: 'docforge-windows-x64',
419+
includeBlockmap: true,
401420
});
402421

403422
const linux = await writeFixtureInstaller(workspace, version, {
@@ -432,6 +451,17 @@ test('release workflow verifies metadata directories across platforms and publis
432451
});
433452

434453
const manifestEntries = new Set(readManifestEntries(await fs.readFile(manifestPath, 'utf8')));
454+
const publishedNames = new Map();
455+
for (const entry of manifestEntries) {
456+
const [relativePath, explicitName] = entry.split('#');
457+
const candidateName = explicitName || path.basename(relativePath);
458+
const previousEntry = publishedNames.get(candidateName);
459+
assert(
460+
!previousEntry,
461+
`Duplicate release asset name detected: ${candidateName} (entries: ${previousEntry}, ${entry})`,
462+
);
463+
publishedNames.set(candidateName, entry);
464+
}
435465
const appUpdateRelative = path.relative(
436466
repoPath(),
437467
path.join(ia32.releaseDir, 'win-ia32-unpacked', 'resources', 'app-update.yml'),
@@ -440,6 +470,24 @@ test('release workflow verifies metadata directories across platforms and publis
440470
!manifestEntries.has(appUpdateRelative),
441471
'app-update.yml should not be uploaded to the release',
442472
);
473+
const ia32InstallerName = `DocForge-Setup-${version}-ia32.exe`;
474+
const x64InstallerName = `DocForge-Setup-${version}.exe`;
475+
const expectedWindowsBinaries = [
476+
path.relative(repoPath(), path.join(ia32.releaseDir, ia32InstallerName)),
477+
path.relative(repoPath(), path.join(x64.releaseDir, x64InstallerName)),
478+
];
479+
for (const entry of expectedWindowsBinaries) {
480+
assert(manifestEntries.has(entry), `${entry} must be included for Windows release assets`);
481+
}
482+
483+
const expectedBlockmaps = [
484+
path.relative(repoPath(), path.join(ia32.releaseDir, `${ia32InstallerName}.blockmap`)),
485+
path.relative(repoPath(), path.join(x64.releaseDir, `${x64InstallerName}.blockmap`)),
486+
];
487+
for (const blockmap of expectedBlockmaps) {
488+
assert(manifestEntries.has(blockmap), `${blockmap} must be published after renaming installers`);
489+
}
490+
443491
const expectedMetadataUploads = [x64.metadataPath, linux.metadataPath, mac.metadataPath];
444492
for (const metadataPath of expectedMetadataUploads) {
445493
assert(metadataPath, 'metadataPath should be defined for uploaded manifests');

0 commit comments

Comments
 (0)