From 106639b3eab06a1f0c0f1344f0d6e3c7f66ba958 Mon Sep 17 00:00:00 2001 From: Ken Jo Date: Tue, 17 Mar 2026 03:15:40 +0900 Subject: [PATCH 1/4] fix: implement dynamic locale detection and enhance error logging --- packages/embedded-postgres/src/index.ts | 29 +++++++++++++++++++------ 1 file changed, 22 insertions(+), 7 deletions(-) diff --git a/packages/embedded-postgres/src/index.ts b/packages/embedded-postgres/src/index.ts index 92f46ec..8659b54 100644 --- a/packages/embedded-postgres/src/index.ts +++ b/packages/embedded-postgres/src/index.ts @@ -2,7 +2,7 @@ import path from 'path'; import crypto from 'crypto'; import fs from 'fs/promises'; import { platform, tmpdir, userInfo } from 'os'; -import { ChildProcess, spawn, exec } from 'child_process'; +import { ChildProcess, spawn, exec, execSync } from 'child_process'; import pg from 'pg'; import AsyncExitHook from 'async-exit-hook'; @@ -19,7 +19,18 @@ const { Client } = pg; * for a particular string, we need to force that string into the right locale. * @see https://github.com/leinelissen/embedded-postgres/issues/15 */ -const LC_MESSAGES_LOCALE = 'en_US.UTF-8'; +function getBestLocale(): string { + try { + const availableLocales = execSync('locale -a', { encoding: 'utf-8' }); + if (availableLocales.includes('en_US.UTF-8')) return 'en_US.UTF-8'; + if (availableLocales.includes('C.UTF-8')) return 'C.UTF-8'; + if (availableLocales.includes('en_US.utf8')) return 'en_US.utf8'; + } catch { + // Fallback to POSIX C locale + } + return 'C'; +} +const LC_MESSAGES_LOCALE = getBestLocale(); /** * Previosuly, options were specified in snake_case rather than camelCase. Old @@ -163,20 +174,24 @@ class EmbeddedPostgres { ], { ...permissionIds, env: { LC_MESSAGES: LC_MESSAGES_LOCALE } }); // Connect to stderr, as that is where the messages get sent + let stderrOutput = ''; process.stdout?.on('data', (chunk: Buffer) => { - // Parse the data as a string and log it const message = chunk.toString('utf-8'); - this.options.onLog(message); + this.options.onLog(message); + }); + + process.stderr?.on('data', (chunk: Buffer) => { + stderrOutput += chunk.toString('utf-8'); + this.options.onLog(`[STDERR] ${chunk.toString('utf-8')}`); }); process.on('exit', (code) => { if (code === 0) { resolve(); } else { - reject(`Postgres init script exited with code ${code}. Please check the logs for extra info. The data directory might already exist.`); + reject(`Postgres init script exited with code ${code}. ERROR OUTPUT: ${stderrOutput}`); } - }); - }); + }); }); // Clean up the file await fs.unlink(passwordFile); From 8f1b46db0aab916621e45d4ba1eb7d18688a33e3 Mon Sep 17 00:00:00 2001 From: Ken Jo Date: Thu, 9 Apr 2026 00:09:20 +0900 Subject: [PATCH 2/4] fix: harden locale detection and initdb process handling - add Windows guard for locale detection - use exact locale matching and per-run locale resolution - merge child env vars, use close event, and ensure pwfile cleanup --- packages/embedded-postgres/src/index.ts | 100 +++++++++++++++--------- 1 file changed, 63 insertions(+), 37 deletions(-) diff --git a/packages/embedded-postgres/src/index.ts b/packages/embedded-postgres/src/index.ts index 8659b54..b11391b 100644 --- a/packages/embedded-postgres/src/index.ts +++ b/packages/embedded-postgres/src/index.ts @@ -20,17 +20,25 @@ const { Client } = pg; * @see https://github.com/leinelissen/embedded-postgres/issues/15 */ function getBestLocale(): string { + // `locale -a` is not available on Windows. + if (platform() === 'win32') { + return 'C'; + } try { - const availableLocales = execSync('locale -a', { encoding: 'utf-8' }); - if (availableLocales.includes('en_US.UTF-8')) return 'en_US.UTF-8'; - if (availableLocales.includes('C.UTF-8')) return 'C.UTF-8'; - if (availableLocales.includes('en_US.utf8')) return 'en_US.utf8'; + const availableLocales = new Set( + execSync('locale -a', { encoding: 'utf-8' }) + .split(/\r?\n/) + .map((locale) => locale.trim()) + .filter(Boolean) + ); + if (availableLocales.has('en_US.UTF-8')) return 'en_US.UTF-8'; + if (availableLocales.has('C.UTF-8')) return 'C.UTF-8'; + if (availableLocales.has('en_US.utf8')) return 'en_US.utf8'; } catch { // Fallback to POSIX C locale } return 'C'; } -const LC_MESSAGES_LOCALE = getBestLocale(); /** * Previosuly, options were specified in snake_case rather than camelCase. Old @@ -115,6 +123,7 @@ class EmbeddedPostgres { */ async initialise() { const { postgres, initdb } = await bin; + const locale = getBestLocale(); // GUARD: Check that a postgres user is available await this.checkForRootUser(); @@ -163,38 +172,48 @@ class EmbeddedPostgres { ensureBinIsExecutable(initdb); // Initialize the database - await new Promise((resolve, reject) => { - const process = spawn(initdb, [ - `--pgdata=${this.options.databaseDir}`, - `--auth=${this.options.authMethod}`, - `--username=${this.options.user}`, - `--pwfile=${passwordFile}`, - `--lc-messages=${LC_MESSAGES_LOCALE}`, - ...this.options.initdbFlags, - ], { ...permissionIds, env: { LC_MESSAGES: LC_MESSAGES_LOCALE } }); - - // Connect to stderr, as that is where the messages get sent - let stderrOutput = ''; - process.stdout?.on('data', (chunk: Buffer) => { - const message = chunk.toString('utf-8'); - this.options.onLog(message); - }); - - process.stderr?.on('data', (chunk: Buffer) => { - stderrOutput += chunk.toString('utf-8'); - this.options.onLog(`[STDERR] ${chunk.toString('utf-8')}`); + try { + await new Promise((resolve, reject) => { + const childProcess = spawn(initdb, [ + `--pgdata=${this.options.databaseDir}`, + `--auth=${this.options.authMethod}`, + `--username=${this.options.user}`, + `--pwfile=${passwordFile}`, + `--lc-messages=${locale}`, + ...this.options.initdbFlags, + ], { + ...permissionIds, + env: { + ...process.env, + LC_MESSAGES: locale, + }, + }); + + // Connect to stderr, as that is where the messages get sent + let stderrOutput = ''; + childProcess.stdout?.on('data', (chunk: Buffer) => { + const message = chunk.toString('utf-8'); + this.options.onLog(message); + }); + + childProcess.stderr?.on('data', (chunk: Buffer) => { + const message = chunk.toString('utf-8'); + stderrOutput += message; + this.options.onLog(`[STDERR] ${message}`); + }); + + childProcess.on('close', (code, signal) => { + if (code === 0) { + resolve(); + } else { + reject(new Error(`Postgres init script failed (code: ${code ?? 'null'}, signal: ${signal ?? 'null'}). ERROR OUTPUT: ${stderrOutput}`)); + } + }); }); - - process.on('exit', (code) => { - if (code === 0) { - resolve(); - } else { - reject(`Postgres init script exited with code ${code}. ERROR OUTPUT: ${stderrOutput}`); - } - }); }); - - // Clean up the file - await fs.unlink(passwordFile); + } finally { + // Clean up the file even when initdb fails + await fs.unlink(passwordFile).catch(() => undefined); + } } /** @@ -204,6 +223,7 @@ class EmbeddedPostgres { */ async start() { const { postgres } = await bin; + const locale = getBestLocale(); // Optionally retrieve the uid and gid const permissionIds = await this.getUidAndGid() @@ -222,7 +242,13 @@ class EmbeddedPostgres { '-p', this.options.port.toString(), ...this.options.postgresFlags, - ], { ...permissionIds, env: { LC_MESSAGES: LC_MESSAGES_LOCALE } }); + ], { + ...permissionIds, + env: { + ...process.env, + LC_MESSAGES: locale, + }, + }); // Connect to stderr, as that is where the messages get sent this.process.stderr?.on('data', (chunk: Buffer) => { From 182b4631ae045c41baa571bce8a9a3e44329e1f9 Mon Sep 17 00:00:00 2001 From: Lei Nelissen Date: Fri, 17 Apr 2026 11:19:21 +0200 Subject: [PATCH 3/4] fix: test with valid non-english locale --- .github/workflows/test.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 4d7bade..c5c09d5 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -28,7 +28,7 @@ jobs: npm test - name: Run test (NL locale) env: - LC_ALL: nl_NL.utf8 + LC_ALL: nl_NL.UTF-8 if: matrix.os != 'windows-latest' run: | cd packages/embedded-postgres From 12034083e42aeb8b7bb4467ba77ccbee321aa736 Mon Sep 17 00:00:00 2001 From: Lei Nelissen Date: Fri, 17 Apr 2026 11:24:27 +0200 Subject: [PATCH 4/4] fix: generate nl locale on ubuntu --- .github/workflows/test.yml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index c5c09d5..a58af45 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -26,6 +26,11 @@ jobs: run: | cd packages/embedded-postgres npm test + - name: Generate NL locale + if: matrix.os == 'ubuntu-latest' + run: | + sudo locale-gen nl_NL.UTF-8 + sudo update-locale - name: Run test (NL locale) env: LC_ALL: nl_NL.UTF-8