From 5afee41ea6720e01cc4b5e6fd26c5df0eb903fbb Mon Sep 17 00:00:00 2001 From: ghou9khub Date: Fri, 16 Jan 2026 00:22:08 -0800 Subject: [PATCH 1/2] Fix graphql endpoint flow. Replaced fetch for host override. --- graphql/codegen/src/cli/commands/generate.ts | 4 +- .../src/cli/introspect/fetch-schema.ts | 70 +++++++++++++------ graphql/server/src/middleware/api.ts | 21 +++--- graphql/server/src/middleware/gql.ts | 2 +- graphql/server/src/types.ts | 2 +- packages/cli/src/commands/codegen.ts | 18 ++++- 6 files changed, 81 insertions(+), 36 deletions(-) diff --git a/graphql/codegen/src/cli/commands/generate.ts b/graphql/codegen/src/cli/commands/generate.ts index 911465612..256f3d118 100644 --- a/graphql/codegen/src/cli/commands/generate.ts +++ b/graphql/codegen/src/cli/commands/generate.ts @@ -31,6 +31,8 @@ export interface GenerateOptions { output?: string; /** Authorization header */ authorization?: string; + /** Additional HTTP headers for endpoint requests */ + headers?: Record; /** Verbose output */ verbose?: boolean; /** Dry run - don't write files */ @@ -93,7 +95,7 @@ export async function generateCommand( endpoint: config.endpoint || undefined, schema: config.schema || undefined, authorization: options.authorization || config.headers['Authorization'], - headers: config.headers, + headers: options.headers || config.headers, }); // 3. Run the codegen pipeline diff --git a/graphql/codegen/src/cli/introspect/fetch-schema.ts b/graphql/codegen/src/cli/introspect/fetch-schema.ts index 3002ac944..d902eb64e 100644 --- a/graphql/codegen/src/cli/introspect/fetch-schema.ts +++ b/graphql/codegen/src/cli/introspect/fetch-schema.ts @@ -2,6 +2,9 @@ * Fetch GraphQL schema introspection from an endpoint */ import { SCHEMA_INTROSPECTION_QUERY } from './schema-query'; +import * as http from 'node:http'; +import * as https from 'node:https'; +import { URL } from 'node:url'; import type { IntrospectionQueryResponse } from '../../types/introspection'; export interface FetchSchemaOptions { @@ -41,59 +44,82 @@ export async function fetchSchema( requestHeaders['Authorization'] = authorization; } - // Create abort controller for timeout const controller = new AbortController(); const timeoutId = setTimeout(() => controller.abort(), timeout); try { - const response = await fetch(endpoint, { + const url = new URL(endpoint); + const isHttps = url.protocol === 'https:'; + const client = isHttps ? https : http; + const body = JSON.stringify({ query: SCHEMA_INTROSPECTION_QUERY, variables: {} }); + const reqHeaders: Record = { + ...requestHeaders, + 'Content-Length': Buffer.byteLength(body), + }; + + const reqOptions: http.RequestOptions = { + protocol: url.protocol, + hostname: url.hostname, + port: url.port ? Number(url.port) : isHttps ? 443 : 80, + path: url.pathname + url.search, method: 'POST', - headers: requestHeaders, - body: JSON.stringify({ - query: SCHEMA_INTROSPECTION_QUERY, - variables: {}, - }), - signal: controller.signal, + headers: reqHeaders, + }; + + const jsonResult = await new Promise<{ statusCode: number; json: any }>((resolve, reject) => { + const req = client.request(reqOptions, (res) => { + const chunks: Buffer[] = []; + res.on('data', (c) => chunks.push(c as Buffer)); + res.on('end', () => { + try { + const text = Buffer.concat(chunks).toString('utf8'); + const parsed = text ? JSON.parse(text) : {}; + resolve({ statusCode: res.statusCode || 0, json: parsed }); + } catch (e) { + reject(e); + } + }); + }); + req.on('error', reject); + req.setTimeout(timeout, () => { + req.destroy(new Error(`Request timeout after ${timeout}ms`)); + }); + req.write(body); + req.end(); }); clearTimeout(timeoutId); - if (!response.ok) { + const { statusCode = 0, json } = jsonResult; + if (statusCode < 200 || statusCode >= 300) { return { success: false, - error: `HTTP ${response.status}: ${response.statusText}`, - statusCode: response.status, + error: `HTTP ${statusCode}: ${json?.errors?.[0]?.message || 'Request failed'}`, + statusCode, }; } - const json = (await response.json()) as { - data?: IntrospectionQueryResponse; - errors?: Array<{ message: string }>; - }; - - // Check for GraphQL errors if (json.errors && json.errors.length > 0) { - const errorMessages = json.errors.map((e) => e.message).join('; '); + const errorMessages = json.errors.map((e: any) => e.message).join('; '); return { success: false, error: `GraphQL errors: ${errorMessages}`, - statusCode: response.status, + statusCode, }; } - // Check if __schema is present if (!json.data?.__schema) { return { success: false, error: 'No __schema field in response. Introspection may be disabled on this endpoint.', - statusCode: response.status, + statusCode, }; } return { success: true, data: json.data, - statusCode: response.status, + statusCode, }; } catch (err) { clearTimeout(timeoutId); diff --git a/graphql/server/src/middleware/api.ts b/graphql/server/src/middleware/api.ts index cf4a82fe6..355643e68 100644 --- a/graphql/server/src/middleware/api.ts +++ b/graphql/server/src/middleware/api.ts @@ -29,7 +29,7 @@ const transformServiceToApi = (svc: Service): ApiStructure => { const schemaNames = api.apiExtensions?.nodes?.map((n: SchemaNode) => n.schemaName) || []; const additionalSchemas = - api.schemasByApiSchemaApiIdAndSchemaId?.nodes?.map((n: SchemaNode) => n.schemaName) || []; + api.schemataByApiSchemaApiIdAndSchemaId?.nodes?.map((n: SchemaNode) => n.schemaName) || []; let domains: string[] = []; if (api.database?.sites?.nodes) { @@ -161,6 +161,12 @@ const getHardCodedSchemata = ({ databaseId: string; key: string; }): any => { + const schemaNodes = schemata + .split(',') + .map((schema) => schema.trim()) + .filter(Boolean) + .map((schemaName) => ({ schemaName })); + const svc = { data: { api: { @@ -169,13 +175,8 @@ const getHardCodedSchemata = ({ dbname: opts.pg.database, anonRole: 'administrator', roleName: 'administrator', - schemaNamesFromExt: { - nodes: schemata - .split(',') - .map((schema) => schema.trim()) - .map((schemaName) => ({ schemaName })), - }, - schemaNames: { nodes: [] as Array<{ schemaName: string }> }, + apiExtensions: { nodes: schemaNodes }, + schemataByApiSchemaApiIdAndSchemaId: { nodes: [] as Array<{ schemaName: string }> }, apiModules: [] as Array, }, }, @@ -203,10 +204,10 @@ const getMetaSchema = ({ dbname: opts.pg.database, anonRole: 'administrator', roleName: 'administrator', - schemaNamesFromExt: { + apiExtensions: { nodes: schemata.map((schemaName: string) => ({ schemaName })), }, - schemaNames: { nodes: [] as Array<{ schemaName: string }> }, + schemataByApiSchemaApiIdAndSchemaId: { nodes: [] as Array<{ schemaName: string }> }, apiModules: [] as Array, }, }, diff --git a/graphql/server/src/middleware/gql.ts b/graphql/server/src/middleware/gql.ts index beb9ae1f8..eacba6cab 100644 --- a/graphql/server/src/middleware/gql.ts +++ b/graphql/server/src/middleware/gql.ts @@ -19,7 +19,7 @@ const apiSelect = { select: { schemaName: true }, connection: true, }, - schemasByApiSchemaApiIdAndSchemaId: { + schemataByApiSchemaApiIdAndSchemaId: { select: { schemaName: true }, connection: true, }, diff --git a/graphql/server/src/types.ts b/graphql/server/src/types.ts index 81eb83f4d..9b2899cf7 100644 --- a/graphql/server/src/types.ts +++ b/graphql/server/src/types.ts @@ -64,7 +64,7 @@ export interface OldApiStructure { dbname: string; anonRole: string; roleName: string; - schemasByApiSchemaApiIdAndSchemaId: SchemaNodes; + schemataByApiSchemaApiIdAndSchemaId: SchemaNodes; apiExtensions: SchemaNodes; apiModules: ApiModuleNodes; rlsModule?: RlsModule; diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index df220bb96..bac15e47a 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -15,8 +15,10 @@ Options: --help, -h Show this help message --config Path to graphql-codegen config file --endpoint GraphQL endpoint URL + --schema Path to GraphQL schema file (.graphql) --auth Authorization header value (e.g., "Bearer 123") - --out Output directory (default: graphql/codegen/dist) + --header "Name: Value" Optional HTTP header; repeat to add multiple + --out Output directory --dry-run Preview without writing files -v, --verbose Verbose output @@ -45,6 +47,19 @@ export default async ( const options: ConstructiveOptions = selectedDb ? getEnvOptions({ pg: { database: selectedDb } }) : getEnvOptions() const schemasArg = (argv.schemas as string) || '' + // Parse repeatable --header args into a headers object + const headerArg = argv.header as string | string[] | undefined + const headerList = Array.isArray(headerArg) ? headerArg : headerArg ? [headerArg] : [] + const headers: Record = {} + for (const h of headerList) { + const idx = typeof h === 'string' ? h.indexOf(':') : -1 + if (idx <= 0) continue + const name = h.slice(0, idx).trim() + const value = h.slice(idx + 1).trim() + if (!name) continue + headers[name] = value + } + const runGenerate = async ({ endpoint, schema }: { endpoint?: string; schema?: string }) => { const result = await generateCommand({ config: configPath || undefined, @@ -52,6 +67,7 @@ export default async ( schema, output: outDir, authorization: auth || undefined, + headers, verbose, dryRun, }) From ca0e2cfc9a3fd53dc51dc9e4a2e2a239f9e9d7fc Mon Sep 17 00:00:00 2001 From: ghou9khub Date: Fri, 16 Jan 2026 00:25:52 -0800 Subject: [PATCH 2/2] Remove extra option --- packages/cli/src/commands/codegen.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/cli/src/commands/codegen.ts b/packages/cli/src/commands/codegen.ts index bac15e47a..06a555392 100644 --- a/packages/cli/src/commands/codegen.ts +++ b/packages/cli/src/commands/codegen.ts @@ -15,7 +15,6 @@ Options: --help, -h Show this help message --config Path to graphql-codegen config file --endpoint GraphQL endpoint URL - --schema Path to GraphQL schema file (.graphql) --auth Authorization header value (e.g., "Bearer 123") --header "Name: Value" Optional HTTP header; repeat to add multiple --out Output directory