diff --git a/graphql/codegen/src/core/codegen/barrel.ts b/graphql/codegen/src/core/codegen/barrel.ts index 9189ea82a..7eb68110c 100644 --- a/graphql/codegen/src/core/codegen/barrel.ts +++ b/graphql/codegen/src/core/codegen/barrel.ts @@ -230,6 +230,59 @@ export function generateRootBarrel(options: RootBarrelOptions = {}): string { return generateCode(statements); } +// ============================================================================ +// Multi-target root barrel (re-exports each target as a namespace) +// ============================================================================ + +/** JS reserved words that need an alias when used as export names */ +const JS_RESERVED = new Set([ + 'abstract', 'arguments', 'await', 'boolean', 'break', 'byte', 'case', 'catch', + 'char', 'class', 'const', 'continue', 'debugger', 'default', 'delete', 'do', + 'double', 'else', 'enum', 'eval', 'export', 'extends', 'false', 'final', + 'finally', 'float', 'for', 'function', 'goto', 'if', 'implements', 'import', + 'in', 'instanceof', 'int', 'interface', 'let', 'long', 'native', 'new', + 'null', 'package', 'private', 'protected', 'public', 'return', 'short', + 'static', 'super', 'switch', 'synchronized', 'this', 'throw', 'throws', + 'transient', 'true', 'try', 'typeof', 'var', 'void', 'volatile', 'while', + 'with', 'yield', +]); + +/** + * Generate a root index.ts for multi-target output that re-exports each + * target as a namespace. + * + * Example output: + * export * as admin from './admin'; + * export * as auth from './auth'; + * export * as public_ from './public'; + */ +export function generateMultiTargetBarrel(targetNames: string[]): string { + const statements: t.Statement[] = []; + + for (const name of targetNames) { + const alias = JS_RESERVED.has(name) ? `${name}_` : name; + const exportDecl = t.exportNamedDeclaration( + null, + [t.exportNamespaceSpecifier(t.identifier(alias))], + t.stringLiteral(`./${name}`), + ); + statements.push(exportDecl); + } + + if (statements.length > 0) { + addJSDocComment(statements[0], [ + '@constructive-io/sdk', + '', + 'Auto-generated GraphQL types and ORM client.', + 'Run `pnpm run generate` to populate this package from the schema files.', + '', + '@generated by @constructive-io/graphql-codegen', + ]); + } + + return generateCode(statements); +} + // ============================================================================ // Custom operation barrels (includes both table and custom hooks) // ============================================================================ diff --git a/graphql/codegen/src/core/generate.ts b/graphql/codegen/src/core/generate.ts index 3b9459560..46a7e8364 100644 --- a/graphql/codegen/src/core/generate.ts +++ b/graphql/codegen/src/core/generate.ts @@ -18,7 +18,7 @@ import type { CliConfig, DbConfig, GraphQLSDKConfigTarget, PgpmConfig, SchemaCon import { getConfigOptions } from '../types/config'; import type { Operation, Table, TypeRegistry } from '../types/schema'; import { generate as generateReactQueryFiles } from './codegen'; -import { generateRootBarrel } from './codegen/barrel'; +import { generateRootBarrel, generateMultiTargetBarrel } from './codegen/barrel'; import { generateCli as generateCliFiles, generateMultiTargetCli } from './codegen/cli'; import type { MultiTargetCliTarget } from './codegen/cli'; import { @@ -827,16 +827,35 @@ export async function generateMulti( } } - // Generate root-root README if multi-target + // Generate root-root README and barrel if multi-target if (names.length > 1 && targetInfos.length > 0 && !dryRun) { - const rootReadme = generateRootRootReadme(targetInfos); const { writeGeneratedFiles: writeFiles } = await import('./output'); + + const rootReadme = generateRootRootReadme(targetInfos); await writeFiles( [{ path: rootReadme.fileName, content: rootReadme.content }], '.', [], { pruneStaleFiles: false }, ); + + // Write a root barrel (index.ts) that re-exports each target as a + // namespace so the package has a single entry-point. Derive the + // common output root from the first target's output path. + const successfulNames = results + .filter((r) => r.result.success) + .map((r) => r.name); + if (successfulNames.length > 0) { + const firstOutput = getConfigOptions(configs[successfulNames[0]]).output; + const outputRoot = path.dirname(firstOutput); + const barrelContent = generateMultiTargetBarrel(successfulNames); + await writeFiles( + [{ path: 'index.ts', content: barrelContent }], + outputRoot, + [], + { pruneStaleFiles: false }, + ); + } } } finally {