diff --git a/LICENSE b/LICENSE index 5b18900a3c..0627732a39 100644 --- a/LICENSE +++ b/LICENSE @@ -469,11 +469,11 @@ THE SOFTWARE. electron-builder dependency patches -Source: https://www.npmjs.com/package/app-builder-lib/v/26.15.3 +Source: https://www.npmjs.com/package/app-builder-lib/v/26.16.1 https://www.npmjs.com/package/electron-updater/v/6.8.9 Repository: https://github.com/electron-userland/electron-builder -Versions: app-builder-lib 26.15.3; electron-updater 6.8.9 -Dependency patch: patches/app-builder-lib+26.15.3.patch +Versions: app-builder-lib 26.16.1; electron-updater 6.8.9 +Dependency patch: patches/app-builder-lib+26.16.1.patch patches/electron-updater+6.8.9.patch License: MIT diff --git a/apps/desktop/package.json b/apps/desktop/package.json index e00b65f73d..330f57afbb 100644 --- a/apps/desktop/package.json +++ b/apps/desktop/package.json @@ -89,7 +89,7 @@ "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "electron": "43.4.1", - "electron-builder": "26.15.3", + "electron-builder": "26.16.1", "esbuild": "^0.28.1", "linkedom": "^0.18.13", "react": "^19.3.0", diff --git a/package-lock.json b/package-lock.json index 407af3e752..c36323cfff 100644 --- a/package-lock.json +++ b/package-lock.json @@ -81,7 +81,7 @@ "@xterm/addon-fit": "^0.11.0", "@xterm/xterm": "^6.0.0", "electron": "43.4.1", - "electron-builder": "26.15.3", + "electron-builder": "26.16.1", "esbuild": "^0.28.1", "linkedom": "^0.18.13", "react": "^19.3.0", @@ -1973,9 +1973,9 @@ } }, "node_modules/@electron/universal/node_modules/@electron/asar/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -2004,9 +2004,9 @@ "license": "MIT" }, "node_modules/@electron/universal/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -2061,9 +2061,9 @@ } }, "node_modules/@electron/universal/node_modules/glob/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -3701,13 +3701,13 @@ } }, "node_modules/@noble/hashes": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-2.2.0.tgz", - "integrity": "sha512-IYqDGiTXab6FniAgnSdZwgWbomxpy9FtYvLKs7wCUs2a8RkITG+DFGO1DM9cr+E3/RgADRpFjrKVaJ1z6sjtEg==", + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@noble/hashes/-/hashes-1.8.0.tgz", + "integrity": "sha512-jCs9ldd7NwzpgXDIf6P3+NrHh9/sD6CQdxHyjQI+h/6rDNo88ypBxxz45UDuZHz9r3tNz7N/VInSVoVdtXEI4A==", "dev": true, "license": "MIT", "engines": { - "node": ">= 20.19.0" + "node": "^14.21.3 || >=16" }, "funding": { "url": "https://paulmillr.com/funding/" @@ -4400,15 +4400,18 @@ ] }, "node_modules/@peculiar/asn1-schema": { - "version": "2.8.0", - "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.8.0.tgz", - "integrity": "sha512-7YT0U/ze0tF2QOBbE15gKZwy5tvgGyLRiRHLzhlbOpf7BT032oBSd0haZqXn5W6l26WLlu3dyxzjM+2638/z2Q==", + "version": "2.9.4", + "resolved": "https://registry.npmjs.org/@peculiar/asn1-schema/-/asn1-schema-2.9.4.tgz", + "integrity": "sha512-GjzePcT9Iw8NzeOPf73iNS9xM+TBhd/FilAfP+RQGkTMQJTVWtytN3JHJACCjf/ABNau5S7mS3g+DcuxmRgYEg==", "dev": true, "license": "MIT", "dependencies": { "@peculiar/utils": "^2.0.2", "asn1js": "^3.0.10", "tslib": "^2.8.1" + }, + "engines": { + "node": ">=14" } }, "node_modules/@peculiar/json-schema": { @@ -6474,9 +6477,9 @@ } }, "node_modules/app-builder-lib": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.15.3.tgz", - "integrity": "sha512-2VnyWkqsP5v5XbBhL3tD5Syx8iNPBYsoU7kY4S2fz7wg8Rj/nztWKCUzGKaFRTv0Xwf3/H058CR1Kvtd/3lRow==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/app-builder-lib/-/app-builder-lib-26.16.1.tgz", + "integrity": "sha512-FhaO6YOup01ZfQW0Z6gt3AyukJjv1gW4uFK47jTgwcHZKqyN/fSlK2LqPf9tAeZYLP2bRJLDzeOkRImsw2X4Pg==", "dev": true, "license": "MIT", "dependencies": { @@ -6488,13 +6491,13 @@ "@electron/rebuild": "^4.0.4", "@electron/universal": "2.0.3", "@malept/flatpak-bundler": "^0.4.0", - "@noble/hashes": "^2.2.0", + "@noble/hashes": "^1.8.0", "@peculiar/webcrypto": "^1.7.1", "@types/fs-extra": "9.0.13", "ajv": "^8.18.0", "asn1js": "^3.0.10", "async-exit-hook": "^2.0.1", - "builder-util": "26.15.3", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chromium-pickle-js": "^0.2.0", "ci-info": "4.3.1", @@ -6502,7 +6505,7 @@ "dotenv": "^16.4.5", "dotenv-expand": "^11.0.6", "ejs": "^3.1.8", - "electron-publish": "26.15.3", + "electron-publish": "26.16.0", "fs-extra": "^10.1.0", "hosted-git-info": "^4.1.0", "isbinaryfile": "^5.0.0", @@ -6526,8 +6529,8 @@ "node": ">=14.0.0" }, "peerDependencies": { - "dmg-builder": "26.15.3", - "electron-builder-squirrel-windows": "26.15.3" + "dmg-builder": "26.16.1", + "electron-builder-squirrel-windows": "26.16.1" } }, "node_modules/app-builder-lib/node_modules/@electron/asar": { @@ -6616,9 +6619,9 @@ "license": "MIT" }, "node_modules/app-builder-lib/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -7336,9 +7339,9 @@ "license": "MIT" }, "node_modules/builder-util": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.15.3.tgz", - "integrity": "sha512-q2hn7Mbo2nFNkVekPiHFx6Nfo3hURmES3tfBn+k5Pqxl2RkmP3QGqZUhH/q9Pch/4G05NRhPjDlVj1O8q4Txvw==", + "version": "26.16.0", + "resolved": "https://registry.npmjs.org/builder-util/-/builder-util-26.16.0.tgz", + "integrity": "sha512-RLyJhB7Si3YkzKR9ubQslWuXW3Vhs3CGe1i+SeixBZ0qTd1mk3XBmssvY22TlB6CS5blyko8Gu1JzpYk8UkYAg==", "dev": true, "license": "MIT", "dependencies": { @@ -8857,9 +8860,9 @@ "license": "MIT" }, "node_modules/dir-compare/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "dependencies": { @@ -8897,14 +8900,14 @@ } }, "node_modules/dmg-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.15.3.tgz", - "integrity": "sha512-O3zJUFUYHJKgzPqioHxfxzBzlSC1eXCSr79gMSBKBP5AgjjpmrydMsMLotEg9fAJF36vdUncb+4ndRNxoPdlSQ==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/dmg-builder/-/dmg-builder-26.16.1.tgz", + "integrity": "sha512-pnI/3Qb24Uk+rMTgIUrsVUKosVgwmBUdF8Zeb8TexOSbpq8MWc7v6l+n+FrEqVkjNZwzBN+XpDS9ENgZ/rkWAw==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "fs-extra": "^10.1.0", "js-yaml": "^4.1.0" } @@ -9111,18 +9114,18 @@ } }, "node_modules/electron-builder": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.15.3.tgz", - "integrity": "sha512-a1KM5heqS3gQCZzizXEI8RjJy3QVogULPdeSknt76uLDpBIW/HDGsMg/XgP0riP6PI9COsRvFITKKGDqA8fJxA==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/electron-builder/-/electron-builder-26.16.1.tgz", + "integrity": "sha512-LrLK65QX5PUYYODXqp23FKrV7CILTtVY7mrJckNknO9jLNSMiqFkKbSMiDRw4CjOADMPVDdWLxY4mezOZWswxg==", "dev": true, "license": "MIT", "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "ci-info": "^4.2.0", - "dmg-builder": "26.15.3", + "dmg-builder": "26.16.1", "fs-extra": "^10.1.0", "lazy-val": "^1.0.5", "simple-update-notifier": "2.0.0", @@ -9137,15 +9140,15 @@ } }, "node_modules/electron-builder-squirrel-windows": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.15.3.tgz", - "integrity": "sha512-Jc19XPV9y9+2bAdZPkXuVNGNIEFBq9poHC61l8Kv6FdK7DRG3+Ic0rerC0DXOaeHNz8yW0fg/JnF8GQROOF5MA==", + "version": "26.16.1", + "resolved": "https://registry.npmjs.org/electron-builder-squirrel-windows/-/electron-builder-squirrel-windows-26.16.1.tgz", + "integrity": "sha512-w0y44wSaT1l6R7CAGmeHn4nHPfvzDyCAU1xJyi1w9SbPYJpYn76SmHDzqHf8Y7l91cPWTdPYBpGQtB2T5mJ08A==", "dev": true, "license": "MIT", "peer": true, "dependencies": { - "app-builder-lib": "26.15.3", - "builder-util": "26.15.3", + "app-builder-lib": "26.16.1", + "builder-util": "26.16.0", "electron-winstaller": "5.4.0" } }, @@ -9222,15 +9225,15 @@ } }, "node_modules/electron-publish": { - "version": "26.15.3", - "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.15.3.tgz", - "integrity": "sha512-g/2bn8YTavY4cuS5F+jOS7zmZbXXBV8KZ8yHKfJjFPoKtzBqrpCdNPxBd3tqdBwP7BVd0lGzf7Bk2s0KesWZ4Q==", + "version": "26.16.0", + "resolved": "https://registry.npmjs.org/electron-publish/-/electron-publish-26.16.0.tgz", + "integrity": "sha512-Vt3KzQIiw9BImvNOYtndg9Mjki+tl4+1sQiC/+G5j8khWaENOJFWodiB+sUl6yyHwtd37avehskdtPw7f8y/+Q==", "dev": true, "license": "MIT", "dependencies": { "@types/fs-extra": "^9.0.11", "aws4": "^1.13.2", - "builder-util": "26.15.3", + "builder-util": "26.16.0", "builder-util-runtime": "9.7.0", "chalk": "^4.1.2", "form-data": "^4.0.5", @@ -9324,9 +9327,9 @@ "peer": true }, "node_modules/electron-winstaller/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, @@ -9900,9 +9903,9 @@ "license": "MIT" }, "node_modules/filelist/node_modules/brace-expansion": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.4.tgz", - "integrity": "sha512-hGfVzPxthbf3+2yjg/RBs60cB0FhqBS/zvdV/4wn4/BmN0bNMMHPc4V/BbFieqf1TKAGGAHnY4eSjajCl0f2Xg==", + "version": "2.1.7", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.1.7.tgz", + "integrity": "sha512-uZbew1NqdmPDTMJ8ah1y+b+9QEJrfkXFk3RcTQw3X0jW/xRUvFKsg1CfQdSYGdTbXZWExtU3J3ccxtnfw1Fi0g==", "dev": true, "license": "MIT", "dependencies": { @@ -12730,9 +12733,9 @@ } }, "node_modules/node-abi": { - "version": "4.33.0", - "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.33.0.tgz", - "integrity": "sha512-vLBWCKb+7LWsX+TbfzWOkw0W81m377tyx3hOweBTjO43CXZnRGS1/JPWs20fr0PgZyDXk6ROYrylsEycK8raDA==", + "version": "4.35.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-4.35.0.tgz", + "integrity": "sha512-ymk4aIzxdPopw2giv8Fs1Ec6vybGkjmyxUwVqhkI4MCy2tVfXdkOGGWieWVjL0THgH+7a8lRdevyupoYj3Js/Q==", "dev": true, "license": "MIT", "dependencies": { @@ -14461,9 +14464,9 @@ "peer": true }, "node_modules/rimraf/node_modules/brace-expansion": { - "version": "1.1.18", - "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.18.tgz", - "integrity": "sha512-Edep/X9fGqVNmzKBVsDYIOtD+z1tuezV70LBjdCst9Tqu76lsnvRiZ6oTic1n+/BIwX6QDGAO94PN4N2SADvtw==", + "version": "1.1.21", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.21.tgz", + "integrity": "sha512-9zeA+KLZNNzglF2TPKRQEDyx6Yby7daAkuy8MiPzpXPsYDWi/DRM8jmwUDxokQjYqBpv5DgPiwD4h4ZZSy1Ujw==", "dev": true, "license": "MIT", "peer": true, diff --git a/patches/app-builder-lib+26.15.3.patch b/patches/app-builder-lib+26.16.1.patch similarity index 84% rename from patches/app-builder-lib+26.15.3.patch rename to patches/app-builder-lib+26.16.1.patch index bbd3bb6520..32abdf80ec 100644 --- a/patches/app-builder-lib+26.15.3.patch +++ b/patches/app-builder-lib+26.16.1.patch @@ -1,7 +1,8 @@ diff --git a/node_modules/app-builder-lib/out/targets/archive.js b/node_modules/app-builder-lib/out/targets/archive.js +index 9595b4f..fd8fe92 100644 --- a/node_modules/app-builder-lib/out/targets/archive.js +++ b/node_modules/app-builder-lib/out/targets/archive.js -@@ -101,6 +101,7 @@ function compute7zCompressArgs(format, options = {}) { +@@ -144,6 +144,7 @@ function compute7zCompressArgs(format, options = {}) { // For all other formats the codec is implicit from the output file extension. args.push(`-mm=${storeOnly ? "Copy" : "Deflate"}`); args.push("-mcu"); diff --git a/scripts/verify-macos-dmg.mjs b/scripts/verify-macos-dmg.mjs index 21a5e6a05b..fb1dbb1470 100644 --- a/scripts/verify-macos-dmg.mjs +++ b/scripts/verify-macos-dmg.mjs @@ -121,6 +121,67 @@ function assertSingleArchitecture(output, subject, expectedArch) { } } +/** + * macOS draws the 16px and 32px faces of an ICNS from the ARGB slots `ic04` + * and `ic05`. PNG data packed into the legacy `icp4`/`icp5`/`icp6` slots is + * not decoded, so it renders as noise — which is what a Finder list row, the + * Dock's Trash, Activity Monitor and the DMG's own volume icon show. Every + * larger size keeps working, so nothing else in this verifier notices. + * + * electron-builder shipped that exact shape from 26.15.2 through 26.15.3 + * (electron-userland/electron-builder#9940). The icon is generated by a + * toolset the builder downloads at build time, so the bundle is the only + * place the result can be seen: assert on it here rather than on a version + * pin that says nothing about what was produced. + */ +const UNRENDERABLE_ICNS_SLOTS = new Map([ + ['icp4', '16x16'], + ['icp5', '32x32'], + ['icp6', '64x64'], +]); +const RENDERED_ICNS_SLOTS = new Map([ + ['ic04', '16x16'], + ['ic05', '32x32'], +]); +const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +function readIcnsSlots(icns) { + if (icns.length < 8 || icns.subarray(0, 4).toString('latin1') !== 'icns') { + throw new Error('Maka icon is not an ICNS archive.'); + } + const slots = new Map(); + // Every entry declares its own length including the 8-byte header, so a zero + // or truncated length is the difference between a parse error and a loop. + for (let offset = 8; offset + 8 <= icns.length; ) { + const type = icns.subarray(offset, offset + 4).toString('latin1'); + const length = icns.readUInt32BE(offset + 4); + if (length < 8 || offset + length > icns.length) { + throw new Error(`Maka icon entry ${type} declares an unusable length of ${length}.`); + } + slots.set(type, icns.subarray(offset + 8, offset + length)); + offset += length; + } + return slots; +} + +export async function assertRenderableAppIcon(resourcesPath, { readIcon = readFile } = {}) { + const slots = readIcnsSlots(await readIcon(join(resourcesPath, 'icon.icns'))); + const unrenderable = [...UNRENDERABLE_ICNS_SLOTS] + .filter(([type]) => slots.get(type)?.subarray(0, PNG_SIGNATURE.length).equals(PNG_SIGNATURE)) + .map(([type, size]) => `${size} (${type})`); + if (unrenderable.length > 0) { + throw new Error( + `Maka icon stores PNG data in ICNS slots macOS does not render: ${unrenderable.join(', ')}.`, + ); + } + const missing = [...RENDERED_ICNS_SLOTS] + .filter(([type]) => !slots.has(type)) + .map(([type, size]) => `${size} (${type})`); + if (missing.length > 0) { + throw new Error(`Maka icon is missing the sizes macOS draws smallest: ${missing.join(', ')}.`); + } +} + async function readPlistValue(run, infoPlist, key) { const { stdout } = await run('plutil', ['-extract', key, 'raw', '-o', '-', infoPlist]); return stdout.trim(); @@ -167,6 +228,7 @@ export async function verifyPackagedMacApp( await requirePath(executable); await assertPackagedResources(resources, { requirePath, forbidPath }); + await assertRenderableAppIcon(resources); await assertPackagedUpdateConfiguration(resources, { channel }); await assertPackagedDependencyClosure(resources); diff --git a/scripts/verify-packaged-app.test.mjs b/scripts/verify-packaged-app.test.mjs index 9abda0c910..d9f2bf9e10 100644 --- a/scripts/verify-packaged-app.test.mjs +++ b/scripts/verify-packaged-app.test.mjs @@ -238,6 +238,31 @@ describe('asarLookupPath', () => { const roots = []; +const PNG_SIGNATURE = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]); + +/** An ICNS archive carrying `slots` verbatim, so a test can state the exact + * entry shape a generator produced without shipping a binary fixture. */ +function icnsWith(slots) { + const entries = slots.map(([type, payload]) => { + const entry = Buffer.alloc(8 + payload.length); + entry.write(type, 0, 'latin1'); + entry.writeUInt32BE(entry.length, 4); + payload.copy(entry, 8); + return entry; + }); + const body = Buffer.concat(entries); + const header = Buffer.alloc(8); + header.write('icns', 0, 'latin1'); + header.writeUInt32BE(header.length + body.length, 4); + return Buffer.concat([header, body]); +} + +const RENDERABLE_ICNS = icnsWith([ + ['ic04', Buffer.from('ARGBfixture')], + ['ic05', Buffer.from('ARGBfixture')], + ['ic07', Buffer.concat([PNG_SIGNATURE, Buffer.from('128px')])], +]); + const PTY_PACKAGES = ['@xterm/headless', '@xterm/addon-unicode11']; const COVERING_NOTICES = 'Header\n\nPackage: react@19.2.0\nDeclared license: MIT\n'; @@ -333,6 +358,7 @@ test('accepts the Intel Mach-O architecture for an x64 package', async () => { join(resources, 'app-update.yml'), 'provider: github\nowner: apache\nrepo: maka\nchannel: dev\nupdaterCacheDirName: "@makadesktop-updater"\n', ); + await writeFile(join(resources, 'icon.icns'), RENDERABLE_ICNS); const version = '0.2.0-dev.14.20260902'; const app = join(dirname(resources), 'Maka.app'); await mkdir(join(app, 'Contents'), { recursive: true }); @@ -367,6 +393,51 @@ test('accepts the Intel Mach-O architecture for an x64 package', async () => { }); }); +describe('assertRenderableAppIcon', () => { + const withIcon = async (t, icns) => { + const resources = await mkdtemp(join(tmpdir(), 'maka-icon-')); + t.after(() => rm(resources, { recursive: true, force: true })); + await writeFile(join(resources, 'icon.icns'), icns); + return resources; + }; + + test('accepts an icon whose small sizes are stored as ARGB', async (t) => { + const { assertRenderableAppIcon } = await import('./verify-macos-dmg.mjs'); + await assertRenderableAppIcon(await withIcon(t, RENDERABLE_ICNS)); + }); + + test('rejects PNG data in the legacy slots macOS does not decode', async (t) => { + const { assertRenderableAppIcon } = await import('./verify-macos-dmg.mjs'); + // The shape electron-builder 26.15.2-26.15.3 produced: every size present, + // the large ones fine, and 16px/32px unreadable where a person sees them. + const resources = await withIcon( + t, + icnsWith([ + ['icp4', Buffer.concat([PNG_SIGNATURE, Buffer.from('16px')])], + ['icp5', Buffer.concat([PNG_SIGNATURE, Buffer.from('32px')])], + ['ic07', Buffer.concat([PNG_SIGNATURE, Buffer.from('128px')])], + ]), + ); + await assert.rejects(assertRenderableAppIcon(resources), /16x16 \(icp4\), 32x32 \(icp5\)/); + }); + + test('rejects an icon that carries no small sizes at all', async (t) => { + const { assertRenderableAppIcon } = await import('./verify-macos-dmg.mjs'); + const resources = await withIcon( + t, + icnsWith([['ic07', Buffer.concat([PNG_SIGNATURE, Buffer.from('128px')])]]), + ); + await assert.rejects(assertRenderableAppIcon(resources), /missing the sizes/); + }); + + test('refuses to guess at a truncated entry instead of looping on it', async (t) => { + const { assertRenderableAppIcon } = await import('./verify-macos-dmg.mjs'); + const icns = icnsWith([['ic04', Buffer.from('ARGBfixture')]]); + icns.writeUInt32BE(0, 12); + await assert.rejects(assertRenderableAppIcon(await withIcon(t, icns)), /unusable length/); + }); +}); + describe('assertPackagedDependencyClosure', () => { test('accepts an artifact whose asar, bundle record, and shipped notices match', async () => { const resources = await makeResources();