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/);
+ }
+});