Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 7 additions & 13 deletions graphql/codegen/src/__tests__/codegen/schema-only.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ const EXAMPLE_SCHEMA = path.resolve(
'../../../examples/example.schema.graphql',
);

describe('generate() with schemaOnly', () => {
describe('generate() with schema.enabled', () => {
let tempDir: string;

beforeEach(() => {
Expand All @@ -23,8 +23,7 @@ describe('generate() with schemaOnly', () => {
it('writes SDL to file from schemaFile source', async () => {
const result = await generate({
schemaFile: EXAMPLE_SCHEMA,
schemaOnly: true,
schemaOnlyOutput: tempDir,
schema: { enabled: true, output: tempDir },
});

expect(result.success).toBe(true);
Expand All @@ -38,12 +37,10 @@ describe('generate() with schemaOnly', () => {
expect(sdl).toContain('type User');
});

it('uses custom filename when schemaOnlyFilename is set', async () => {
it('uses custom filename when schema.filename is set', async () => {
const result = await generate({
schemaFile: EXAMPLE_SCHEMA,
schemaOnly: true,
schemaOnlyOutput: tempDir,
schemaOnlyFilename: 'app.graphql',
schema: { enabled: true, output: tempDir, filename: 'app.graphql' },
});

expect(result.success).toBe(true);
Expand All @@ -54,8 +51,7 @@ describe('generate() with schemaOnly', () => {
it('succeeds without any generators enabled', async () => {
const result = await generate({
schemaFile: EXAMPLE_SCHEMA,
schemaOnly: true,
schemaOnlyOutput: tempDir,
schema: { enabled: true, output: tempDir },
});

expect(result.success).toBe(true);
Expand All @@ -64,8 +60,7 @@ describe('generate() with schemaOnly', () => {

it('fails when no source is specified', async () => {
const result = await generate({
schemaOnly: true,
schemaOnlyOutput: tempDir,
schema: { enabled: true, output: tempDir },
});

expect(result.success).toBe(false);
Expand All @@ -77,8 +72,7 @@ describe('generate() with schemaOnly', () => {

const result = await generate({
schemaFile: EXAMPLE_SCHEMA,
schemaOnly: true,
schemaOnlyOutput: nestedDir,
schema: { enabled: true, output: nestedDir },
});

expect(result.success).toBe(true);
Expand Down
17 changes: 13 additions & 4 deletions graphql/codegen/src/cli/handler.ts
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,13 @@ export async function runCodegenHandler(
): Promise<void> {
const args = camelizeArgv(argv as Record<string, any>);

const schemaOnly = Boolean(args.schemaOnly);
const schemaConfig = args.schemaEnabled
? {
enabled: true,
...(args.schemaOutput ? { output: String(args.schemaOutput) } : {}),
...(args.schemaFilename ? { filename: String(args.schemaFilename) } : {}),
}
: undefined;

const hasSourceFlags = Boolean(
args.endpoint || args.schemaFile || args.schemaDir || args.schemas || args.apiNames
Expand Down Expand Up @@ -80,7 +86,7 @@ export async function runCodegenHandler(
const { results, hasError } = await generateMulti({
configs: selectedTargets,
cliOverrides: cliOptions as Partial<GraphQLSDKConfigTarget>,
schemaOnly,
schema: schemaConfig,
});

for (const { name, result } of results) {
Expand All @@ -107,7 +113,7 @@ export async function runCodegenHandler(
if (expanded) {
const { results, hasError } = await generateMulti({
configs: expanded,
schemaOnly,
schema: schemaConfig,
});
for (const { name, result } of results) {
console.log(`\n[${name}]`);
Expand All @@ -117,6 +123,9 @@ export async function runCodegenHandler(
return;
}

const result = await generate({ ...options, schemaOnly });
const result = await generate({
...options,
...(schemaConfig ? { schema: schemaConfig } : {}),
});
printResult(result);
}
8 changes: 6 additions & 2 deletions graphql/codegen/src/cli/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,9 +36,11 @@ Generator Options:
-v, --verbose Show detailed output

Schema Export:
--schema-only Export GraphQL SDL instead of running full codegen.
--schema-enabled Export GraphQL SDL instead of running full codegen.
Works with any source (endpoint, file, database, PGPM).
With multiple apiNames, writes one .graphql per API.
--schema-output <dir> Output directory for the exported schema file
--schema-filename <name> Filename for the exported schema (default: schema.graphql)

-h, --help Show this help message
--version Show version number
Expand Down Expand Up @@ -77,12 +79,14 @@ export const options: Partial<CLIOptions> = {
a: 'authorization',
v: 'verbose',
},
boolean: ['schema-only'],
boolean: ['schema-enabled'],
string: [
'config',
'endpoint',
'schema-file',
'schema-dir',
'schema-output',
'schema-filename',
'output',
'target',
'authorization',
Expand Down
27 changes: 14 additions & 13 deletions graphql/codegen/src/core/generate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ import { pgCache } from 'pg-cache';
import { createEphemeralDb, type EphemeralDbResult } from 'pgsql-client';
import { deployPgpm } from 'pgsql-seed';

import type { CliConfig, DbConfig, GraphQLSDKConfigTarget, PgpmConfig } from '../types/config';
import type { CliConfig, DbConfig, GraphQLSDKConfigTarget, PgpmConfig, SchemaConfig } from '../types/config';
import { getConfigOptions } from '../types/config';
import type { CleanOperation, CleanTable, TypeRegistry } from '../types/schema';
import { generate as generateReactQueryFiles } from './codegen';
Expand Down Expand Up @@ -64,9 +64,6 @@ export interface GenerateOptions extends GraphQLSDKConfigTarget {
verbose?: boolean;
dryRun?: boolean;
skipCustomOperations?: boolean;
schemaOnly?: boolean;
schemaOnlyOutput?: string;
schemaOnlyFilename?: string;
}

export interface GenerateResult {
Expand Down Expand Up @@ -140,7 +137,9 @@ export async function generate(
options.nodeHttpAdapter === true ||
(runCli && options.nodeHttpAdapter !== false);

if (!options.schemaOnly && !runReactQuery && !runOrm && !runCli) {
const schemaEnabled = !!options.schema?.enabled;

if (!schemaEnabled && !runReactQuery && !runOrm && !runCli) {
return {
success: false,
message:
Expand Down Expand Up @@ -171,7 +170,7 @@ export async function generate(
headers: config.headers,
});

if (options.schemaOnly) {
if (schemaEnabled && !runReactQuery && !runOrm && !runCli) {
try {
console.log(`Fetching schema from ${source.describe()}...`);
const { introspection } = await source.fetch();
Expand All @@ -186,9 +185,9 @@ export async function generate(
};
}

const outDir = path.resolve(options.schemaOnlyOutput || outputRoot || '.');
const outDir = path.resolve(options.schema?.output || outputRoot || '.');
await fs.promises.mkdir(outDir, { recursive: true });
const filename = options.schemaOnlyFilename || 'schema.graphql';
const filename = options.schema?.filename || 'schema.graphql';
const filePath = path.join(outDir, filename);
await fs.promises.writeFile(filePath, sdl, 'utf-8');

Expand Down Expand Up @@ -550,7 +549,7 @@ export interface GenerateMultiOptions {
cliOverrides?: Partial<GraphQLSDKConfigTarget>;
verbose?: boolean;
dryRun?: boolean;
schemaOnly?: boolean;
schema?: SchemaConfig;
unifiedCli?: CliConfig | boolean;
}

Expand Down Expand Up @@ -669,13 +668,14 @@ function applySharedPgpmDb(
export async function generateMulti(
options: GenerateMultiOptions,
): Promise<GenerateMultiResult> {
const { configs, cliOverrides, verbose, dryRun, schemaOnly, unifiedCli } = options;
const { configs, cliOverrides, verbose, dryRun, schema, unifiedCli } = options;
const names = Object.keys(configs);
const results: Array<{ name: string; result: GenerateResult }> = [];
let hasError = false;

const schemaEnabled = !!schema?.enabled;
const targetInfos: RootRootReadmeTarget[] = [];
const useUnifiedCli = !schemaOnly && !!unifiedCli && names.length > 1;
const useUnifiedCli = !schemaEnabled && !!unifiedCli && names.length > 1;

const cliTargets: MultiTargetCliTarget[] = [];

Expand All @@ -693,8 +693,9 @@ export async function generateMulti(
...targetConfig,
verbose,
dryRun,
schemaOnly,
schemaOnlyFilename: schemaOnly ? `${name}.graphql` : undefined,
schema: schemaEnabled
? { ...schema, filename: schema?.filename ?? `${name}.graphql` }
: targetConfig.schema,
},
useUnifiedCli ? { skipCli: true, targetName: name } : { targetName: name },
);
Expand Down
33 changes: 33 additions & 0 deletions graphql/codegen/src/types/config.ts
Original file line number Diff line number Diff line change
Expand Up @@ -173,6 +173,32 @@ export interface DocsConfig {
skills?: boolean;
}

/**
* Schema export configuration
* Controls SDL schema export behavior.
*/
export interface SchemaConfig {
/**
* Enable schema SDL export
* When true, fetches the schema and writes it as a .graphql SDL file.
* If no generators are enabled (orm, reactQuery, cli), only the schema is exported.
* @default false
*/
enabled?: boolean;

/**
* Output directory for the exported schema file
* @default same as the target's output directory
*/
output?: string;

/**
* Filename for the exported schema file
* @default 'schema.graphql'
*/
filename?: string;
}

/**
* Infrastructure command name overrides for collision handling.
* When a target name collides with a default infra command name,
Expand Down Expand Up @@ -390,6 +416,13 @@ export interface GraphQLSDKConfigTarget {
*/
docs?: DocsConfig | boolean;

/**
* Schema export configuration
* When enabled, exports the GraphQL SDL to a file.
* If no generators are also enabled, this acts as a schema-only export.
*/
schema?: SchemaConfig;

/**
* Custom path for generated skill files.
* When set, skills are written to this directory.
Expand Down
2 changes: 1 addition & 1 deletion graphql/codegen/src/types/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -49,7 +49,7 @@ export type {
} from './selection';

// Config types
export type { GraphQLSDKConfig, GraphQLSDKConfigTarget } from './config';
export type { GraphQLSDKConfig, GraphQLSDKConfigTarget, SchemaConfig } from './config';
export {
DEFAULT_CONFIG,
defineConfig,
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/AGENTS.md
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@ The CLI provides GraphQL-focused commands:

- `packages/cli/src/commands/server.ts` – start the Constructive GraphQL server
- `packages/cli/src/commands/explorer.ts` – start the Constructive GraphQL explorer
- `packages/cli/src/commands/codegen.ts` – run GraphQL codegen (`@constructive-io/graphql-codegen`), including `--schema-only` for SDL export
- `packages/cli/src/commands/codegen.ts` – run GraphQL codegen (`@constructive-io/graphql-codegen`), including `--schema-enabled` for SDL export

## Debugging Tips

Expand Down
16 changes: 11 additions & 5 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -107,26 +107,32 @@ cnc codegen --api-names my_api --output ./codegen --orm
- `--dry-run` - Preview without writing files
- `--verbose` - Verbose output

### `cnc codegen --schema-only`
### `cnc codegen --schema-enabled`

Export GraphQL schema SDL without running full code generation. Works with any source (endpoint, file, database, PGPM).

```bash
# From database schemas
cnc codegen --schema-only --schemas myapp,public --output ./schemas
cnc codegen --schema-enabled --schemas myapp,public --schema-output ./schemas

# From running server
cnc codegen --schema-only --endpoint http://localhost:3000/graphql --output ./schemas
cnc codegen --schema-enabled --endpoint http://localhost:3000/graphql --schema-output ./schemas

# From schema file (useful for converting/validating)
cnc codegen --schema-only --schema-file ./input.graphql --output ./schemas
cnc codegen --schema-enabled --schema-file ./input.graphql --schema-output ./schemas

# From a directory of .graphql files (multi-target)
cnc codegen --schema-only --schema-dir ./schemas --output ./exported
cnc codegen --schema-enabled --schema-dir ./schemas --schema-output ./exported

# Custom filename
cnc codegen --schema-enabled --endpoint http://localhost:3000/graphql --schema-output ./schemas --schema-filename public.graphql
```

**Options:**

- `--schema-enabled` - Enable schema SDL export
- `--schema-output <dir>` - Output directory for the exported schema file
- `--schema-filename <name>` - Filename for the exported schema (default: schema.graphql)
- `--endpoint <url>` - GraphQL endpoint URL
- `--schema-file <path>` - Path to GraphQL schema file
- `--schemas <list>` - Comma-separated PostgreSQL schemas
Expand Down
4 changes: 3 additions & 1 deletion packages/cli/src/commands/codegen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,9 +26,11 @@ Generator Options:
--verbose Verbose output

Schema Export:
--schema-only Export GraphQL SDL instead of running full codegen.
--schema-enabled Export GraphQL SDL instead of running full codegen.
Works with any source (endpoint, file, database, PGPM).
With multiple apiNames, writes one .graphql per API.
--schema-output <dir> Output directory for the exported schema file
--schema-filename <name> Filename for the exported schema (default: schema.graphql)

--help, -h Show this help message
`;
Expand Down
2 changes: 1 addition & 1 deletion packages/cli/src/utils/display.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,7 @@ export const usageText = `
cnc server --port 8080 Start server on custom port
cnc explorer Launch GraphiQL explorer
cnc codegen --schema schema.graphql Generate types from schema
cnc codegen --schema-only --out schema.graphql Export schema SDL
cnc codegen --schema-enabled --output ./schemas Export schema SDL
cnc jobs up Start combined server (jobs runtime)

# Execution Engine
Expand Down
Loading