From 8f00000a4d5fe966adc155c40a70b375d46fca5d Mon Sep 17 00:00:00 2001 From: Tony Ketcham Date: Fri, 17 Jul 2026 15:28:57 -0700 Subject: [PATCH] refactor(config): loadConfig returns an initialized LoadedFlatbreadConfig Make the config package the single owner of "a loaded, initialized config". loadConfig already called initializeConfig internally, but its return type claimed raw FlatbreadConfig, so downstream callers defensively re-initialized: codegen's CLI, watchAndGenerate's config-reload branch, and flatbread's loadFlatbreadConfig each ran a second initializeConfig over an already-initialized config. - loadConfig now returns ConfigResult; the three redundant re-inits are deleted. - initializeConfig has exactly two call sites: the loader and FlatbreadProvider (the programmatic entry point that accepts raw config from user code). - Typed load errors (NoConfigFoundError, TooManyConfigsFoundError) surface without process exit; process.exit stays only in CLI wrappers. Test plan: new AVA unit tests in packages/config cover both error modes and assert a successful load returns an initialized (normalized) config. Full suite green: pnpm verify (lint + typecheck + build + 243 AVA + 52 vitest). Co-authored-by: Cursor Change-Id: I0bf57aa7bf50e9bb90fcb3432d36349ac0c74ff3 --- packages/codegen/src/cli.ts | 11 +--- packages/codegen/src/generator.ts | 3 +- packages/config/src/load.test.ts | 70 +++++++++++++++++++++++ packages/config/src/load.ts | 3 +- packages/flatbread/src/utils/getSchema.ts | 3 +- 5 files changed, 76 insertions(+), 14 deletions(-) create mode 100644 packages/config/src/load.test.ts diff --git a/packages/codegen/src/cli.ts b/packages/codegen/src/cli.ts index 36b40e81..4c69e42e 100644 --- a/packages/codegen/src/cli.ts +++ b/packages/codegen/src/cli.ts @@ -1,4 +1,4 @@ -import { generateSchema, type LoadedFlatbreadConfig } from '@flatbread/core'; +import { generateSchema } from '@flatbread/core'; import { loadConfig } from '@flatbread/config'; import kleur from 'kleur'; import type { CodegenOptions } from './types.js'; @@ -63,14 +63,7 @@ export function createCodegenCommand() { cwd: options.config ? dirname(options.config) : process.cwd(), }); - if (!configResult.config) { - console.error(kleur.red('✗ Failed to load Flatbread configuration')); - process.exit(1); - } - - // Initialize the configuration first - const { initializeConfig } = await import('@flatbread/core'); - const loadedConfig = initializeConfig(configResult.config); + const loadedConfig = configResult.config!; // Generate GraphQL schema if (options.verbose) { diff --git a/packages/codegen/src/generator.ts b/packages/codegen/src/generator.ts index 8ee5c127..a41fdffd 100644 --- a/packages/codegen/src/generator.ts +++ b/packages/codegen/src/generator.ts @@ -669,11 +669,10 @@ export async function watchAndGenerate( if (path.includes('flatbread.config.')) { try { const { loadConfig } = await import('@flatbread/config'); - const { initializeConfig } = await import('@flatbread/core'); const configResult = await loadConfig({ cwd: process.cwd() }); if (configResult.config) { - currentConfig = initializeConfig(configResult.config); + currentConfig = configResult.config; console.log(kleur.dim('🔧 Configuration reloaded')); // Refresh codegen options from the updated config so changes like outputFile/outputDir/documents are applied currentOptions = deriveOptionsFromConfig( diff --git a/packages/config/src/load.test.ts b/packages/config/src/load.test.ts new file mode 100644 index 00000000..4fab39f5 --- /dev/null +++ b/packages/config/src/load.test.ts @@ -0,0 +1,70 @@ +import test from 'ava'; +import fs from 'node:fs/promises'; +import os from 'node:os'; +import path from 'node:path'; +import type { LoadedFlatbreadConfig } from '@flatbread/core'; +import { NoConfigFoundError, TooManyConfigsFoundError } from './errors'; +import { loadConfig } from './load'; + +async function withTempConfig( + files: Record, + callback: (cwd: string) => Promise +) { + const cwd = await fs.mkdtemp(path.join(os.tmpdir(), 'flatbread-config-')); + + try { + await Promise.all( + Object.entries(files).map(([filename, contents]) => + fs.writeFile(path.join(cwd, filename), contents) + ) + ); + await callback(cwd); + } finally { + await fs.rm(cwd, { recursive: true, force: true }); + } +} + +test('loadConfig throws NoConfigFoundError without exiting', async (t) => { + await withTempConfig({}, async (cwd) => { + const error = await t.throwsAsync(loadConfig({ cwd })); + + t.true(error instanceof NoConfigFoundError); + }); +}); + +test('loadConfig throws TooManyConfigsFoundError without exiting', async (t) => { + await withTempConfig( + { + 'flatbread.config.js': 'export default {};', + 'flatbread.config.ts': 'export default {};', + }, + async (cwd) => { + const error = await t.throwsAsync(loadConfig({ cwd })); + + t.true(error instanceof TooManyConfigsFoundError); + } + ); +}); + +test('loadConfig returns an initialized config', async (t) => { + await withTempConfig( + { + 'flatbread.config.js': ` + export default { + source: { fetch: async () => ({}) }, + transformer: { extensions: ['.md'], inspect: (input) => String(input) }, + content: [], + }; + `, + }, + async (cwd) => { + const result = await loadConfig({ cwd }); + const config = result.config as LoadedFlatbreadConfig; + + t.is(result.filepath, path.join(cwd, 'flatbread.config.js')); + t.is(config.transformer.length, 1); + t.deepEqual(config.loaded.extensions, ['.md']); + t.is(typeof config.fieldNameTransform, 'function'); + } + ); +}); diff --git a/packages/config/src/load.ts b/packages/config/src/load.ts index 7094c711..fa8a5612 100644 --- a/packages/config/src/load.ts +++ b/packages/config/src/load.ts @@ -1,6 +1,7 @@ import { ConfigResult, FlatbreadConfig, + LoadedFlatbreadConfig, initializeConfig, } from '@flatbread/core'; import { build } from 'esbuild'; @@ -62,7 +63,7 @@ async function loadConfigFromBundledFile( * @returns Promise that resolves to the user config object. */ export async function loadConfig({ cwd = process.cwd() } = {}): Promise< - ConfigResult + ConfigResult > { let configFileName: ConfigFileName; const files = await fs.readdir(cwd); diff --git a/packages/flatbread/src/utils/getSchema.ts b/packages/flatbread/src/utils/getSchema.ts index ee1c5bdf..ccf24c76 100644 --- a/packages/flatbread/src/utils/getSchema.ts +++ b/packages/flatbread/src/utils/getSchema.ts @@ -29,6 +29,5 @@ export async function loadFlatbreadConfig( const { loadConfig } = await import('@flatbread/config'); const result = await loadConfig({ cwd }); if (!result.config) throw new Error('Flatbread configuration was not found'); - const { initializeConfig } = await import('@flatbread/core'); - return { ...result, config: initializeConfig(result.config) }; + return result; }