also looking at this
fix(packaging): stop macOS packages relocating themselves elsewhere
#46
4 files
+164
-16
| @@ -2,7 +2,7 @@ import { afterEach, describe, expect, it } from 'bun:test' | ||
| 2 | 2 | import { chmodSync, existsSync, mkdtempSync, readFileSync, rmSync, writeFileSync } from 'node:fs' |
| 3 | 3 | import { tmpdir } from 'node:os' |
| 4 | 4 | import { join } from 'node:path' |
| 5 | import { candleArguments, codesignArguments, dmgCapacityMegabytes, dmgCreateArguments, formatPackagingCommandError, macOSInfoPlist, notarytoolArguments, packageApp, productbuildArguments, renderWixSource, shouldRetryHdiutil, windowsArchitecture, windowsExecutableName } from './package.js' | |
| 5 | import { candleArguments, codesignArguments, dmgCapacityMegabytes, dmgCreateArguments, formatPackagingCommandError, macOSInfoPlist, notarytoolArguments, packageApp, pkgbuildArguments, pkgbuildComponentPlist, productbuildArguments, renderWixSource, shouldRetryHdiutil, windowsArchitecture, windowsExecutableName } from './package.js' | |
| 6 | 6 | |
| 7 | 7 | describe('Windows MSI packaging', () => { |
| 8 | 8 | it('renders a deterministic major-upgrade installer without shell interpolation', () => { |
| @@ -51,6 +51,61 @@ describe('Windows MSI packaging', () => { | ||
| 51 | 51 | }) |
| 52 | 52 | }) |
| 53 | 53 | |
| 54 | describe('macOS pkg relocation', () => { | |
| 55 | it('pins the install path instead of letting the bundle be relocated', () => { | |
| 56 | const plist = pkgbuildComponentPlist('Applications/Craft App.app') | |
| 57 | ||
| 58 | // The one that matters. Left to pkgbuild, this is true, and the package | |
| 59 | // then carries <relocate><bundle id="..."/></relocate> — which lets | |
| 60 | // `installer` write the payload over any copy of that bundle the system | |
| 61 | // has registered elsewhere and still exit 0. "The install was successful" | |
| 62 | // with nothing at /Applications is that, and it is intermittent because it | |
| 63 | // depends on what the system has indexed. | |
| 64 | expect(plist).toContain('<key>BundleIsRelocatable</key>\n <false/>') | |
| 65 | ||
| 66 | // Off so that installing an older version over a newer one is not skipped; | |
| 67 | // with it on, a rollback silently becomes a no-op. | |
| 68 | expect(plist).toContain('<key>BundleIsVersionChecked</key>\n <false/>') | |
| 69 | ||
| 70 | expect(plist).toContain('<string>Applications/Craft App.app</string>') | |
| 71 | expect(plist).toContain('<?xml version="1.0" encoding="UTF-8"?>') | |
| 72 | }) | |
| 73 | ||
| 74 | it('escapes the bundle path rather than emitting broken XML', () => { | |
| 75 | expect(pkgbuildComponentPlist('Applications/Ben & Co.app')).toContain('Applications/Ben & Co.app') | |
| 76 | }) | |
| 77 | ||
| 78 | it('always passes the component plist to pkgbuild', () => { | |
| 79 | const args = pkgbuildArguments({ | |
| 80 | root: '/tmp/stage/root', | |
| 81 | componentPlistPath: '/tmp/stage/component.plist', | |
| 82 | identifier: 'dev.craft.lifecycle', | |
| 83 | version: '1.0.0', | |
| 84 | outputPath: '/tmp/out.pkg', | |
| 85 | }) | |
| 86 | expect(args).toEqual([ | |
| 87 | '--root', '/tmp/stage/root', | |
| 88 | '--component-plist', '/tmp/stage/component.plist', | |
| 89 | '--identifier', 'dev.craft.lifecycle', | |
| 90 | '--version', '1.0.0', | |
| 91 | '--install-location', '/', | |
| 92 | '/tmp/out.pkg', | |
| 93 | ]) | |
| 94 | ||
| 95 | // Signing is additive and must not displace the component plist. | |
| 96 | const signed = pkgbuildArguments({ | |
| 97 | root: '/tmp/stage/root', | |
| 98 | componentPlistPath: '/tmp/stage/component.plist', | |
| 99 | identifier: 'dev.craft.lifecycle', | |
| 100 | version: '1.0.0', | |
| 101 | outputPath: '/tmp/out.pkg', | |
| 102 | installerIdentity: '3rd Party Mac Developer Installer: Example', | |
| 103 | }) | |
| 104 | expect(signed).toContain('--component-plist') | |
| 105 | expect(signed.slice(-3, -1)).toEqual(['--sign', '3rd Party Mac Developer Installer: Example']) | |
| 106 | }) | |
| 107 | }) | |
| 108 | ||
| 54 | 109 | describe('macOS packaging diagnostics', () => { |
| 55 | 110 | it('retains hdiutil output in a failed package result', () => { |
| 56 | 111 | expect(formatPackagingCommandError('hdiutil', 1, '', 'create failed - Resource temporarily unavailable\n')) |