import { readFile } from 'node:fs/promises'; import { join } from 'node:path'; import { isPlainObject } from '@sindresorhus/is'; import { coerceArray } from '../util/array.ts'; interface FileConfig { /** Top-level keys that must appear first, in the given order. */ firstKeys?: string[]; } const fileConfigs: Record = { 'abandonments.json': {}, 'changelog-urls.json': {}, 'monorepo.json': {}, 'replacements.json': { firstKeys: ['all'] }, 'source-urls.json': {}, }; const files = Object.keys(fileConfigs); function checkKeysSorted( obj: Record, path: string, depth: number, firstKeys?: string[], ): void { let keys = Object.keys(obj); const expectedFirst = depth === 0 ? coerceArray(firstKeys) : []; expect( keys.slice(0, expectedFirst.length), `${path} should start with [${expectedFirst.join(', ')}]`, ).toStrictEqual(expectedFirst); keys = keys.slice(expectedFirst.length); expect(keys, `${path} keys should be sorted alphabetically`).toStrictEqual( [...keys].sort(), ); for (const [key, value] of Object.entries(obj)) { if (isPlainObject(value)) { checkKeysSorted(value, `${path}.${key}`, depth + 1); } } } describe('data/index', () => { for (const file of files) { describe(`${file}`, () => { let data: Record; beforeAll(async () => { data = JSON.parse( await readFile(join(import.meta.dirname, file), 'utf8'), ) as Record; }); it('keys are sorted alphabetically', () => { const { firstKeys } = fileConfigs[file]; const root = Object.fromEntries( Object.entries(data).filter(([k]) => k !== '$schema'), ); checkKeysSorted(root, file, 0, firstKeys); }); }); } });