@@ -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 (
305313test ( '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