From 7f51fc019b3ac1e9ebf3a80058ba2e72442db8ac Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Martin=20Ku=C4=8Dera?= Date: Mon, 1 Jun 2026 16:35:27 +0200 Subject: [PATCH] fix: sort discovered locales for deterministic generator output `getAllLocales` returned locale folders in the order `fs.readdir` yielded them, which is filesystem-dependent and not stable across platforms or runs. This caused the generated `Locales` type and `locales` array to be reordered on regeneration even when nothing changed, producing noisy diffs. Sort the discovered locales so the output is deterministic. --- .../generator/test/get-all-locales.test.ts | 44 +++++++++++++++++++ packages/shared/src/file.utils.mts | 5 ++- 2 files changed, 48 insertions(+), 1 deletion(-) create mode 100644 packages/generator/test/get-all-locales.test.ts diff --git a/packages/generator/test/get-all-locales.test.ts b/packages/generator/test/get-all-locales.test.ts new file mode 100644 index 00000000..81955134 --- /dev/null +++ b/packages/generator/test/get-all-locales.test.ts @@ -0,0 +1,44 @@ +import { suite } from 'uvu' +import * as assert from 'uvu/assert' +import { getAllLocales, type FileSystemUtil } from '../../shared/src/file.utils.mjs' + +const test = suite('getAllLocales') + +type Dirent = { name: string; isDirectory: () => boolean } + +const folder = (name: string): Dirent => ({ name, isDirectory: () => true }) +const file = (name: string): Dirent => ({ name, isDirectory: () => false }) + +const ROOT = './i18n' + +// the order in which the filesystem returns directory entries is not guaranteed +// to be stable across platforms or runs, so `getAllLocales` must sort the result +// to produce a deterministic output +const createFileSystem = (rootEntries: Dirent[]): FileSystemUtil => ({ + readFile: async () => '', + readdir: async (path) => (String(path) === ROOT ? rootEntries : [file('index.ts')]), +}) + +test('returns the discovered locales sorted alphabetically', async () => { + const fs = createFileSystem([folder('sk'), folder('cs'), folder('en')]) + + assert.equal(await getAllLocales(fs, ROOT, 'TypeScript'), ['cs', 'en', 'sk']) +}) + +test('sorts locales independently of the filesystem order', async () => { + const ascending = createFileSystem([folder('cs'), folder('en'), folder('sk')]) + const descending = createFileSystem([folder('sk'), folder('en'), folder('cs')]) + + assert.equal(await getAllLocales(ascending, ROOT, 'TypeScript'), await getAllLocales(descending, ROOT, 'TypeScript')) +}) + +test('also sorts when looking for JavaScript locale files', async () => { + const fs: FileSystemUtil = { + readFile: async () => '', + readdir: async (path) => (String(path) === ROOT ? [folder('sk'), folder('cs')] : [file('index.js')]), + } + + assert.equal(await getAllLocales(fs, ROOT, 'JavaScript'), ['cs', 'sk']) +}) + +test.run() diff --git a/packages/shared/src/file.utils.mts b/packages/shared/src/file.utils.mts index 3c5fb803..f018cf22 100644 --- a/packages/shared/src/file.utils.mts +++ b/packages/shared/src/file.utils.mts @@ -38,5 +38,8 @@ export const getAllLocales = async ( const fileEnding = outputFormat === 'JavaScript' ? '.js' : '.ts' const files = await getFiles(fs, path, 1) - return files.filter(({ folder, name }) => folder && name === `index${fileEnding}`).map(({ folder }) => folder) + return files + .filter(({ folder, name }) => folder && name === `index${fileEnding}`) + .map(({ folder }) => folder) + .sort() }