From b72ca91c3a8e69c36d2bac9d72acb9a051c98e41 Mon Sep 17 00:00:00 2001 From: Emran025 <1.48263577e+08+Emran025@users.noreply.github.com> Date: Sun, 23 Aug 2026 17:52:24 +0000 Subject: [PATCH] fix(macos): keep Server Desktop package at Applications --- .../build-server-desktop-macos-package.mjs | 12 +++++++- frontend/scripts/macos-app-bundle-policy.mjs | 25 ++++++++++++++++ .../scripts/macos-app-bundle-policy.test.mjs | 30 ++++++++++++++++++- 3 files changed, 65 insertions(+), 2 deletions(-) diff --git a/frontend/scripts/build-server-desktop-macos-package.mjs b/frontend/scripts/build-server-desktop-macos-package.mjs index 691a54d3..777a8588 100644 --- a/frontend/scripts/build-server-desktop-macos-package.mjs +++ b/frontend/scripts/build-server-desktop-macos-package.mjs @@ -1,7 +1,10 @@ import { spawn } from 'node:child_process'; import { chmod, cp, mkdir, readFile, readdir, rm, stat, writeFile } from 'node:fs/promises'; import { basename, join, relative, resolve } from 'node:path'; -import { relativeToAppContents } from './macos-app-bundle-policy.mjs'; +import { + nonRelocatableBundleComponentPlist, + relativeToAppContents, +} from './macos-app-bundle-policy.mjs'; import { verifyMachOPayload as verifyContainedMachOPayload } from './macos-macho.mjs'; const [target] = process.argv.slice(2); @@ -24,12 +27,17 @@ const appSource = await findServerDesktopApp(targetRoot); const appContract = await verifyAppBundle(appSource, target); const packageName = `ACCORE.ERP.Server.Desktop_${version}_macos_${targetDefinition.architecture}.pkg`; const packagePath = join(outputRoot, packageName); +const componentPlist = join(stageRoot, 'components.plist'); await rm(stageRoot, { recursive: true, force: true }); await rm(outputRoot, { recursive: true, force: true }); await mkdir(join(stageRoot, 'payload', 'Applications'), { recursive: true }); await mkdir(join(stageRoot, 'scripts'), { recursive: true }); await mkdir(outputRoot, { recursive: true }); +await writeFile( + componentPlist, + nonRelocatableBundleComponentPlist(join('Applications', basename(appSource))) +); const stagedApp = join(stageRoot, 'payload', 'Applications', basename(appSource)); await cp(appSource, stagedApp, { recursive: true, verbatimSymlinks: true }); @@ -38,6 +46,8 @@ await writeInstallerScripts(appContract); await run('pkgbuild', [ '--root', join(stageRoot, 'payload'), + '--component-plist', + componentPlist, '--scripts', join(stageRoot, 'scripts'), '--ownership', diff --git a/frontend/scripts/macos-app-bundle-policy.mjs b/frontend/scripts/macos-app-bundle-policy.mjs index 26001489..a01e1997 100644 --- a/frontend/scripts/macos-app-bundle-policy.mjs +++ b/frontend/scripts/macos-app-bundle-policy.mjs @@ -6,3 +6,28 @@ export function relativeToAppContents(relativePath) { } return normalized.slice(prefix.length); } + +export function nonRelocatableBundleComponentPlist(rootRelativeBundlePath) { + const normalized = rootRelativeBundlePath.replaceAll('\\', '/'); + if ( + !normalized || + normalized.startsWith('/') || + normalized.split('/').some((segment) => segment === '' || segment === '.' || segment === '..') + ) { + throw new Error(`expected a safe destination-root bundle path: ${rootRelativeBundlePath}`); + } + + return ` + + + + + RootRelativeBundlePath + ${normalized} + BundleIsRelocatable + + + + +`; +} diff --git a/frontend/scripts/macos-app-bundle-policy.test.mjs b/frontend/scripts/macos-app-bundle-policy.test.mjs index 054e52e8..c81df0f6 100644 --- a/frontend/scripts/macos-app-bundle-policy.test.mjs +++ b/frontend/scripts/macos-app-bundle-policy.test.mjs @@ -1,6 +1,9 @@ import assert from 'node:assert/strict'; import test from 'node:test'; -import { relativeToAppContents } from './macos-app-bundle-policy.mjs'; +import { + nonRelocatableBundleComponentPlist, + relativeToAppContents, +} from './macos-app-bundle-policy.mjs'; test('converts an app-root relative sidecar path to a Contents-relative path once', () => { assert.equal( @@ -16,3 +19,28 @@ test('converts an app-root relative sidecar path to a Contents-relative path onc test('rejects a sidecar path outside the app Contents root', () => { assert.throws(() => relativeToAppContents('MacOS/accore-server-agent'), /Contents/); }); + +test('creates a component policy that installs the application at its declared destination', () => { + assert.equal( + nonRelocatableBundleComponentPlist('Applications/ACCORE ERP Server Desktop.app'), + ` + + + + + RootRelativeBundlePath + Applications/ACCORE ERP Server Desktop.app + BundleIsRelocatable + + + + +` + ); +}); + +test('rejects unsafe destination-root bundle paths', () => { + for (const path of ['', '/Applications/ACCORE ERP Server Desktop.app', '../Desktop.app']) { + assert.throws(() => nonRelocatableBundleComponentPlist(path), /safe destination-root/); + } +});