From d0d10aae6a3814da4f13b42b66debef507a7b2f0 Mon Sep 17 00:00:00 2001 From: gsxdsm Date: Wed, 22 Jul 2026 14:56:51 -0700 Subject: [PATCH] FN-8499: fix macOS embedded PostgreSQL ICU loader links Ensure embedded PostgreSQL repairs its macOS ICU ABI loader link before startup. - Normalize the libicuuc.68 dylib compatibility symlink from packaged patch versions. - Cover missing, dangling, and valid ICU compatibility links in lifecycle tests. - Add a patch changeset for the macOS startup fix. Files changed: .changeset/fn-8499-macos-postgres-libicu.md | 7 +++++ .../__tests__/postgres/embedded-lifecycle.test.ts | 32 ++++++++++++++++++---- packages/core/src/postgres/embedded-lifecycle.ts | 17 +++++++++--- 3 files changed, 47 insertions(+), 9 deletions(-) Fusion-Task-Id: FN-8499 Fusion-Task-Lineage: 1ca453f4-f619-452c-9112-f32ff8fb8128 Co-authored-by: Fusion (runfusion.ai) --- .changeset/fn-8499-macos-postgres-libicu.md | 7 ++++ .../postgres/embedded-lifecycle.test.ts | 32 ++++++++++++++++--- .../core/src/postgres/embedded-lifecycle.ts | 17 +++++++--- 3 files changed, 47 insertions(+), 9 deletions(-) create mode 100644 .changeset/fn-8499-macos-postgres-libicu.md diff --git a/.changeset/fn-8499-macos-postgres-libicu.md b/.changeset/fn-8499-macos-postgres-libicu.md new file mode 100644 index 0000000000..bee3d24e04 --- /dev/null +++ b/.changeset/fn-8499-macos-postgres-libicu.md @@ -0,0 +1,7 @@ +--- +"@runfusion/fusion": patch +--- + +summary: Fix macOS embedded PostgreSQL startup when bundled ICU compatibility links are missing. +category: fix +dev: Repair the libicuuc loader-name symlink before initdb starts. diff --git a/packages/core/src/__tests__/postgres/embedded-lifecycle.test.ts b/packages/core/src/__tests__/postgres/embedded-lifecycle.test.ts index c2fcb1e37d..c89c6c6102 100644 --- a/packages/core/src/__tests__/postgres/embedded-lifecycle.test.ts +++ b/packages/core/src/__tests__/postgres/embedded-lifecycle.test.ts @@ -490,12 +490,16 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => { writeFileSync(join(libDir, "libzstd.1.5.7.dylib"), ""); writeFileSync(join(libDir, "liblz4.1.10.0.dylib"), ""); writeFileSync(join(libDir, "libz.1.3.2.dylib"), ""); + // This is the loader-name pair in the packaged Darwin payload: i18n + // requests the ABI-specific uc name rather than the unversioned link. writeFileSync(join(libDir, "libicui18n.68.2.dylib"), ""); + writeFileSync(join(libDir, "libicuuc.68.2.dylib"), ""); const created = normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot); expect(created.map((link) => link.expected).sort()).toEqual([ "libicui18n.dylib", + "libicuuc.68.dylib", "liblz4.1.dylib", "libpq.5.dylib", "libz.1.dylib", @@ -503,6 +507,7 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => { ]); expect(readlinkSync(join(libDir, "libpq.5.dylib"))).toBe("libpq.5.15.dylib"); expect(readlinkSync(join(libDir, "libzstd.1.dylib"))).toBe("libzstd.1.5.7.dylib"); + expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.2.dylib"); // Idempotent: the second pass sees the compatibility names and creates nothing. expect(normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot)).toEqual([]); @@ -511,20 +516,37 @@ describe("embedded-lifecycle: macOS dylib compatibility links", () => { } }); - it("replaces stale broken compatibility-name symlinks", () => { + it("selects the highest matching ICU patch and repairs a dangling loader-name link", () => { const nativeRoot = mkdtempSync(join(tmpdir(), "fusion-embedded-native-")); try { const libDir = join(nativeRoot, "lib"); mkdirSync(libDir, { recursive: true }); - writeFileSync(join(libDir, "libpq.5.16.dylib"), ""); - symlinkSync("libpq.5.15.dylib", join(libDir, "libpq.5.dylib")); + writeFileSync(join(libDir, "libicuuc.68.2.dylib"), ""); + writeFileSync(join(libDir, "libicuuc.68.12.dylib"), ""); + symlinkSync("libicuuc.68.1.dylib", join(libDir, "libicuuc.68.dylib")); const created = normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot); expect(created).toEqual([ - { expected: "libpq.5.dylib", target: "libpq.5.16.dylib", created: true }, + { expected: "libicuuc.68.dylib", target: "libicuuc.68.12.dylib", created: true }, ]); - expect(readlinkSync(join(libDir, "libpq.5.dylib"))).toBe("libpq.5.16.dylib"); + expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.12.dylib"); + } finally { + rmSync(nativeRoot, { recursive: true, force: true }); + } + }); + + it("preserves a valid ICU compatibility link and treats absent library trees as no-ops", () => { + const nativeRoot = mkdtempSync(join(tmpdir(), "fusion-embedded-native-")); + try { + const libDir = join(nativeRoot, "lib"); + mkdirSync(libDir, { recursive: true }); + writeFileSync(join(libDir, "libicuuc.68.2.dylib"), ""); + symlinkSync("libicuuc.68.2.dylib", join(libDir, "libicuuc.68.dylib")); + + expect(normalizeMacosEmbeddedPostgresDylibSymlinks(nativeRoot)).toEqual([]); + expect(readlinkSync(join(libDir, "libicuuc.68.dylib"))).toBe("libicuuc.68.2.dylib"); + expect(normalizeMacosEmbeddedPostgresDylibSymlinks(join(nativeRoot, "missing"))).toEqual([]); } finally { rmSync(nativeRoot, { recursive: true, force: true }); } diff --git a/packages/core/src/postgres/embedded-lifecycle.ts b/packages/core/src/postgres/embedded-lifecycle.ts index ad2f0ab679..6fe7a55c12 100644 --- a/packages/core/src/postgres/embedded-lifecycle.ts +++ b/packages/core/src/postgres/embedded-lifecycle.ts @@ -750,12 +750,20 @@ export interface EmbeddedDylibNormalization { readonly created: boolean; } +/* +FNXC:PostgresEmbedded 2026-07-22-14:34: +The bundled libicui18n.68.2.dylib records libicuuc.68.dylib as its loader +name. macOS dyld must find that ABI-specific compatibility name before initdb +runs, so normalize it only from the packaged libicuuc.68..dylib payload +rather than adding a broad unversioned ICU link or relying on system libraries. +*/ const MACOS_EMBEDDED_DYLIB_SYMLINKS: readonly EmbeddedDylibSymlinkSpec[] = [ { expected: "libpq.5.dylib", candidate: /^libpq\.5\..+\.dylib$/ }, { expected: "libzstd.1.dylib", candidate: /^libzstd\.1\..+\.dylib$/ }, { expected: "liblz4.1.dylib", candidate: /^liblz4\.1\..+\.dylib$/ }, { expected: "libz.1.dylib", candidate: /^libz\.1\..+\.dylib$/ }, { expected: "libicui18n.dylib", candidate: /^libicui18n\..+\.dylib$/ }, + { expected: "libicuuc.68.dylib", candidate: /^libicuuc\.68\..+\.dylib$/ }, ]; function sortDylibCandidates(files: readonly string[], candidate: RegExp): string[] { @@ -768,10 +776,11 @@ function sortDylibCandidates(files: readonly string[], candidate: RegExp): strin * Normalize macOS embedded-postgres library names before initdb/postgres spawn. * * The @embedded-postgres/darwin-* packages can contain fully-versioned dylibs - * (for example libpq.5.15.dylib, libzstd.1.5.7.dylib) while the bundled - * binaries link against ABI compatibility names such as libpq.5.dylib and - * libzstd.1.dylib via @loader_path/../lib/.... When the package postinstall - * symlink hydration is skipped or incomplete, dyld fails before initdb can run. + * (for example libpq.5.15.dylib, libzstd.1.5.7.dylib, and + * libicuuc.68.2.dylib) while the bundled binaries link against ABI compatibility + * names such as libpq.5.dylib, libzstd.1.dylib, and libicuuc.68.dylib via + * @loader_path/../lib/.... When the package postinstall symlink hydration is + * skipped or incomplete, dyld fails before initdb can run. * * This is intentionally local to the embedded binary package and idempotent: * existing compatibility names are left alone; missing compatibility names are