Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
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
1 change: 1 addition & 0 deletions graphql/codegen/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -68,6 +68,7 @@
"inflekt": "^0.3.1",
"inquirerer": "^4.4.0",
"jiti": "^2.6.1",
"komoji": "^0.8.0",
"oxfmt": "^0.26.0",
"pg-cache": "workspace:^",
"pg-env": "workspace:^",
Expand Down
8 changes: 6 additions & 2 deletions graphql/codegen/src/core/codegen/barrel.ts
Original file line number Diff line number Diff line change
Expand Up @@ -195,14 +195,15 @@ export interface RootBarrelOptions {
hasTypes?: boolean;
hasHooks?: boolean;
hasOrm?: boolean;
hasCli?: boolean;
}

/**
* Generate the root index.ts barrel file for the output directory.
* Re-exports from subdirectories based on which generators are enabled.
*/
export function generateRootBarrel(options: RootBarrelOptions = {}): string {
const { hasTypes = false, hasHooks = false, hasOrm = false } = options;
const { hasTypes = false, hasHooks = false, hasOrm = false, hasCli = false } = options;
const statements: t.Statement[] = [];

if (hasTypes) {
Expand All @@ -214,8 +215,11 @@ export function generateRootBarrel(options: RootBarrelOptions = {}): string {
if (hasOrm) {
statements.push(exportAllFrom('./orm'));
}
if (hasCli) {
statements.push(exportAllFrom('./cli'));
}
Comment on lines +218 to +220
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🔴 No cli/index.ts barrel generated, causing root barrel export * from './cli' to fail

The root barrel (barrel.ts:218-220) generates export * from './cli' when hasCli is true, but the CLI generator never produces a cli/index.ts file. Module resolution will fail because there is no entry point for the ./cli directory.

Root Cause

Looking at the files generated by generateCli in cli/index.ts:40-69, the output files are:

  • executor.ts
  • commands/context.ts
  • commands/auth.ts
  • commands/<table>.ts (per table)
  • commands/<custom-op>.ts (per custom op)
  • commands.ts

No index.ts barrel is generated for the cli/ directory. Compare with the ORM generator, which produces orm/index.ts via client-generator.ts:338.

When the root barrel emits export * from './cli', TypeScript/Node will try to resolve cli/index.ts (or cli/index.js), which doesn't exist.

Impact: The root index.ts barrel will fail to compile due to the unresolvable ./cli re-export.

Prompt for agents
The CLI generator needs to produce a cli/index.ts barrel file, similar to how the ORM generator produces orm/index.ts (see graphql/codegen/src/core/codegen/orm/client-generator.ts:338). Add a barrel file generation step in graphql/codegen/src/core/codegen/cli/index.ts inside the generateCli function (around line 69) that creates an index.ts file re-exporting from the appropriate CLI modules (e.g., commands.ts, executor.ts). This file should be added to the files array before the function returns.
Open in Devin Review

Was this helpful? React with 👍 or 👎 to provide feedback.


// Add file header as leading comment on first statement
// Add file headeras leading comment on first statement
if (statements.length > 0) {
addJSDocComment(statements[0], [
'Generated SDK - auto-generated, do not edit',
Expand Down
139 changes: 139 additions & 0 deletions graphql/codegen/src/core/codegen/cli/arg-mapper.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
import * as t from '@babel/types';

import type { CleanArgument, CleanTypeRef } from '../../../types/schema';

function unwrapNonNull(typeRef: CleanTypeRef): { inner: CleanTypeRef; required: boolean } {
if (typeRef.kind === 'NON_NULL' && typeRef.ofType) {
return { inner: typeRef.ofType, required: true };
}
return { inner: typeRef, required: false };
}

function resolveBaseType(typeRef: CleanTypeRef): CleanTypeRef {
if ((typeRef.kind === 'NON_NULL' || typeRef.kind === 'LIST') && typeRef.ofType) {
return resolveBaseType(typeRef.ofType);
}
return typeRef;
}

export function buildQuestionObject(arg: CleanArgument): t.ObjectExpression {
const { inner, required } = unwrapNonNull(arg.type);
const base = resolveBaseType(arg.type);
const props: t.ObjectProperty[] = [];

if (base.kind === 'ENUM' && base.enumValues && base.enumValues.length > 0) {
props.push(
t.objectProperty(t.identifier('type'), t.stringLiteral('autocomplete')),
);
props.push(
t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)),
);
props.push(
t.objectProperty(
t.identifier('message'),
t.stringLiteral(arg.description || arg.name),
),
);
props.push(
t.objectProperty(
t.identifier('options'),
t.arrayExpression(base.enumValues.map((v) => t.stringLiteral(v))),
),
);
} else if (base.kind === 'SCALAR' && base.name === 'Boolean') {
props.push(
t.objectProperty(t.identifier('type'), t.stringLiteral('confirm')),
);
props.push(
t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)),
);
props.push(
t.objectProperty(
t.identifier('message'),
t.stringLiteral(arg.description || arg.name),
),
);
props.push(
t.objectProperty(t.identifier('default'), t.booleanLiteral(false)),
);
} else if (
base.kind === 'SCALAR' &&
(base.name === 'Int' || base.name === 'Float')
) {
props.push(
t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
);
props.push(
t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)),
);
props.push(
t.objectProperty(
t.identifier('message'),
t.stringLiteral(arg.description || `${arg.name} (number)`),
),
);
} else if (inner.kind === 'INPUT_OBJECT' && inner.inputFields) {
return buildInputObjectQuestion(arg.name, inner, required);
} else {
props.push(
t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
);
props.push(
t.objectProperty(t.identifier('name'), t.stringLiteral(arg.name)),
);
props.push(
t.objectProperty(
t.identifier('message'),
t.stringLiteral(arg.description || arg.name),
),
);
}

if (required) {
props.push(
t.objectProperty(t.identifier('required'), t.booleanLiteral(true)),
);
}

return t.objectExpression(props);
}

function buildInputObjectQuestion(
_name: string,
typeRef: CleanTypeRef,
_required: boolean,
): t.ObjectExpression {
if (typeRef.inputFields && typeRef.inputFields.length > 0) {
const firstField = typeRef.inputFields[0];
return buildQuestionObject(firstField);
}
return t.objectExpression([
t.objectProperty(t.identifier('type'), t.stringLiteral('text')),
t.objectProperty(t.identifier('name'), t.stringLiteral(_name)),
t.objectProperty(
t.identifier('message'),
t.stringLiteral(_name),
),
]);
}

export function buildQuestionsArray(args: CleanArgument[]): t.ArrayExpression {
const questions: t.Expression[] = [];
for (const arg of args) {
const base = resolveBaseType(arg.type);
const { inner } = unwrapNonNull(arg.type);

if (inner.kind === 'INPUT_OBJECT' && inner.inputFields) {
for (const field of inner.inputFields) {
questions.push(buildQuestionObject(field));
}
} else if (base.kind === 'INPUT_OBJECT' && base.inputFields) {
for (const field of base.inputFields) {
questions.push(buildQuestionObject(field));
}
} else {
questions.push(buildQuestionObject(arg));
}
}
return t.arrayExpression(questions);
}
Loading