Skip to content

fix: implement dynamic locale detection and enhance error logging#26

Merged
leinelissen merged 4 commits into
leinelissen:mainfrom
ken-jo:fix/dynamic-locale-detection
Apr 17, 2026
Merged

fix: implement dynamic locale detection and enhance error logging#26
leinelissen merged 4 commits into
leinelissen:mainfrom
ken-jo:fix/dynamic-locale-detection

Conversation

@ken-jo
Copy link
Copy Markdown
Contributor

@ken-jo ken-jo commented Mar 16, 2026

Description

This PR addresses an issue where initdb fails on systems that do not have the en_US.UTF-8 locale installed (e.g., some WSL environments or minimal Docker images). It also improves the developer experience by capturing and including stderr in error messages.

Changes

  1. Dynamic Locale Detection: Instead of hardcoding en_US.UTF-8, the library now checks available locales using locale -a and prioritizes en_US.UTF-8 > C.UTF-8 > en_US.utf8 > C. This ensures compatibility while maintaining English output for parsing.
  2. Stderr Capturing: Modified the spawn logic for initdb to capture the standard error stream. If initialization fails, the full stderr output is now included in the rejection message, making it much easier to diagnose issues.

Verification

  • Confirmed failure on a system without en_US.UTF-8.
  • Verified that all tests pass after the fix on the same system.

Copilot AI review requested due to automatic review settings April 3, 2026 09:11
@leinelissen leinelissen force-pushed the fix/dynamic-locale-detection branch from a45a47e to 106639b Compare April 3, 2026 09:11
@leinelissen
Copy link
Copy Markdown
Owner

Hey @ken-jo, thanks for your pull request! Sorry for only getting back to you now. This looks promising and I'm inclined to merge it. The only open question is that locale -a is not available on Windows, which is a target that we do support. Could we introduce a check around that and ensure we pass the locale correctly on that platform as well?

Copy link
Copy Markdown

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR improves embedded-postgres initialization robustness across minimal environments by selecting an available locale at runtime and by surfacing initdb stderr output to make failures easier to diagnose.

Changes:

  • Add runtime locale detection (via locale -a) and set LC_MESSAGES_LOCALE dynamically.
  • Capture and log initdb stderr output and include it in the rejection message on failure.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread packages/embedded-postgres/src/index.ts Outdated
Comment on lines +24 to +27
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';
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBestLocale() uses substring checks (includes) on the full locale -a output. This can return a locale that is not actually installed (e.g., if only en_US.UTF-8@something exists, includes('en_US.UTF-8') is true), causing initdb to still fail. Consider splitting by line and doing exact matches (or a regex anchored to line boundaries) before selecting the locale.

Suggested change
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 localeOutput = execSync('locale -a', { encoding: 'utf-8' });
const availableLocales = new Set(
localeOutput
.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';

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
Comment on lines 33 to 34
const LC_MESSAGES_LOCALE = getBestLocale();

Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

getBestLocale() is executed at module load via const LC_MESSAGES_LOCALE = getBestLocale();, which runs a synchronous shell command (execSync('locale -a')) on import. This can add startup latency and can be problematic in restricted environments. Prefer computing this lazily (e.g., during initialise()/start(), or caching on first use) and/or using an async variant to avoid blocking the event loop.

Suggested change
const LC_MESSAGES_LOCALE = getBestLocale();
let lcMessagesLocale: string | undefined;
function getLcMessagesLocale(): string {
if (lcMessagesLocale === undefined) {
lcMessagesLocale = getBestLocale();
}
return lcMessagesLocale;
}

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
@@ -163,20 +174,24 @@ class EmbeddedPostgres {
], { ...permissionIds, env: { LC_MESSAGES: LC_MESSAGES_LOCALE } });
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The spawn(..., { env: { LC_MESSAGES: LC_MESSAGES_LOCALE } }) options object replaces the child process environment entirely, dropping process.env (PATH, LANG, etc.). This can cause initdb to behave differently across environments. Merge with the parent env (e.g., env: { ...process.env, LC_MESSAGES: LC_MESSAGES_LOCALE }) rather than overwriting it.

Suggested change
], { ...permissionIds, env: { LC_MESSAGES: LC_MESSAGES_LOCALE } });
], { ...permissionIds, env: { ...process.env, LC_MESSAGES: LC_MESSAGES_LOCALE } });

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
Comment on lines +183 to +194
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}`);
}
});
});
}); });
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The promise waits for the child process 'exit' event, but 'exit' can fire before stdio streams have fully flushed/closed, so stderrOutput may be incomplete. Use the 'close' event (and include signal if present) to ensure all output is captured before resolving/rejecting.

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
Comment on lines 165 to 197
}); });

// Clean up the file
await fs.unlink(passwordFile);
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The password file is deleted only after the init promise resolves. If initdb fails and the promise rejects, await fs.unlink(passwordFile) is skipped and the plaintext password file may be left behind. Wrap the initdb call in a try/finally (and best-effort unlink in finally) to guarantee cleanup on both success and failure.

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
}
});
});
}); });
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There appears to be a formatting issue on the promise close brace line (}); });), which makes the block hard to read and may violate the project's formatter/linter rules. Please reformat so the closing braces/parentheses are on separate, properly indented lines.

Suggested change
}); });
});
});

Copilot uses AI. Check for mistakes.
Comment thread packages/embedded-postgres/src/index.ts Outdated
Comment on lines +22 to +33
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();
Copy link

Copilot AI Apr 3, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The new locale-selection logic and the new stderr-in-error behavior aren’t covered by tests. Since this package already has Vitest coverage for initialise()/start(), consider adding tests that mock child_process.execSync (and/or spawn) to verify locale prioritization/fallback and that rejected initdb errors include captured stderr.

Copilot uses AI. Check for mistakes.
- 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
@ken-jo
Copy link
Copy Markdown
Contributor Author

ken-jo commented Apr 8, 2026

Thanks for the feedback. After reviewing the Copilot recommendations, I confirmed they align with your requested requirements, so I went ahead and applied those improvements in this PR. I also tested the updated changes on my local Windows machine and verified that they pass.

@ken-jo
Copy link
Copy Markdown
Contributor Author

ken-jo commented Apr 16, 2026

@leinelissen Plz check again :)

@leinelissen leinelissen merged commit 4e9abd9 into leinelissen:main Apr 17, 2026
3 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants