diff --git a/bun.lockb b/bun.lockb index d15b03c4c..a407f8687 100755 Binary files a/bun.lockb and b/bun.lockb differ diff --git a/packages/cli/package.json b/packages/cli/package.json index f8d0d9f85..4bf5d53b7 100644 --- a/packages/cli/package.json +++ b/packages/cli/package.json @@ -54,6 +54,7 @@ "@inquirer/select": "^4.0.0", "@settlemint/sdk-js": "workspace:*", "@settlemint/sdk-utils": "workspace:*", + "get-tsconfig": "^4.8.1", "is-in-ci": "^1.0.0", "yoctocolors": "^2" }, diff --git a/packages/cli/src/commands/codegen/gqltada.spinner.ts b/packages/cli/src/commands/codegen/gqltada.spinner.ts index e35f34ca7..2c3ade6fa 100644 --- a/packages/cli/src/commands/codegen/gqltada.spinner.ts +++ b/packages/cli/src/commands/codegen/gqltada.spinner.ts @@ -1,8 +1,9 @@ -import { existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs"; +import { mkdirSync, writeFileSync } from "node:fs"; import { join } from "node:path"; import { generateSchema } from "@gql.tada/cli-utils"; import { projectRoot } from "@settlemint/sdk-utils/filesystem"; import type { DotEnv } from "@settlemint/sdk-utils/validation"; +import { getTsconfig } from "get-tsconfig"; /** * Writes environment variables to .env files with a spinner for visual feedback. @@ -19,6 +20,25 @@ import type { DotEnv } from "@settlemint/sdk-utils/validation"; * ); */ export async function gqltadaSpinner(env: DotEnv) { + await codegenTsconfig(); + await codegenHasura(env); + await codegenPortal(env); + await codegenTheGraph(env); + await codegenTheGraphFallback(env); +} + +async function codegenTsconfig() { + const tsconfig = getTsconfig(); + if (!tsconfig?.config) { + return; + } + if (!tsconfig.config.compilerOptions) { + tsconfig.config.compilerOptions = {}; + } + if (!tsconfig.config.compilerOptions.plugins) { + tsconfig.config.compilerOptions.plugins = []; + } + const tadaConfig = { name: "@0no-co/graphqlsp", schemas: [ @@ -29,12 +49,12 @@ export async function gqltadaSpinner(env: DotEnv) { }, { name: "thegraph", - schema: "thegraph-schema.graphql", + schema: "the-graph-schema.graphql", tadaOutputLocation: "the-graph-env.d.ts", }, { name: "thegraph-fallback", - schema: "thegraph-fallback-schema.graphql", + schema: "the-graph-fallback-schema.graphql", tadaOutputLocation: "the-graph-fallback-env.d.ts", }, { @@ -45,161 +65,135 @@ export async function gqltadaSpinner(env: DotEnv) { ], }; - const projectDir = await projectRoot(); - const tsconfigFile = join(projectDir, "tsconfig.json"); - const tsconfigContent = readFileSync(tsconfigFile, "utf8"); - const tsconfig: { - compilerOptions: { - plugins?: { - name: string; - schemas: { - name: string; - schema: string; - tadaOutputLocation: string; - }[]; - }[]; - }; - } = JSON.parse(tsconfigContent); - // Find the existing @0no-co/graphqlsp plugin or create a new one - if (!tsconfig.compilerOptions.plugins) { - tsconfig.compilerOptions.plugins = []; + const graphqlspPlugin = tsconfig.config.compilerOptions.plugins.find((plugin) => plugin.name === "@0no-co/graphqlsp"); + if (graphqlspPlugin) { + tsconfig.config.compilerOptions.plugins = tsconfig.config.compilerOptions.plugins.filter( + (plugin) => plugin.name === "@0no-co/graphqlsp", + ); } - let graphqlspPlugin = (tsconfig.compilerOptions.plugins ?? []).find((plugin) => plugin.name === "@0no-co/graphqlsp"); + tsconfig.config.compilerOptions.plugins.push(tadaConfig); - if (!graphqlspPlugin) { - // If the plugin doesn't exist, create it and add it to the plugins array - graphqlspPlugin = { name: "@0no-co/graphqlsp", schemas: [] }; - tsconfig.compilerOptions.plugins.push(graphqlspPlugin); - } + writeFileSync(tsconfig.path, JSON.stringify(tsconfig, null, 2), "utf8"); +} - // Update or add the schemas defined in tadaConfig - for (const schema of tadaConfig.schemas) { - const existingSchemaIndex = graphqlspPlugin.schemas.findIndex((s) => s.name === schema.name); - if (existingSchemaIndex !== -1) { - // Update existing schema - graphqlspPlugin.schemas[existingSchemaIndex] = schema; - } else { - // Add new schema - graphqlspPlugin.schemas.push(schema); - } +async function codegenHasura(env: DotEnv) { + const gqlEndpoint = env.SETTLEMINT_HASURA_ENDPOINT; + if (!gqlEndpoint) { + return; } + const accessToken = env.SETTLEMINT_ACCESS_TOKEN; + const adminSecret = env.SETTLEMINT_HASURA_ADMIN_SECRET!; + + await generateSchema({ + input: gqlEndpoint, + output: "hasura-schema.graphql", + tsconfig: undefined, + headers: { + "x-hasura-admin-secret": adminSecret, + "x-auth-token": accessToken, + }, + }); - // Write the updated tsconfig back to the file - writeFileSync(tsconfigFile, JSON.stringify(tsconfig, null, 2), "utf8"); + const clientTemplate = `import { createServerHasuraClient } from "@settlemint/sdk-hasura"; +import type { introspection } from "../../hasura-env.d.ts"; - await gqltadaCodegen({ - type: "HASURA", - env, - }); - await gqltadaCodegen({ - type: "PORTAL", - env, - }); - await gqltadaCodegen({ - type: "THE_GRAPH", - env, - allowToFail: true, - }); - await gqltadaCodegen({ - type: "THE_GRAPH_FALLBACK", - env, - }); -} +export const { client: hasuraClient, graphql: hasuraGraphql } = createServerHasuraClient<{ + introspection: introspection; + disableMasking: true; + scalars: { + DateTime: Date; + JSON: Record; + }; +}>({ + instance: process.env.SETTLEMINT_HASURA_ENDPOINT!, + accessToken: process.env.SETTLEMINT_ACCESS_TOKEN!, + adminSecret: process.env.SETTLEMINT_HASURA_ADMIN_SECRET!, +});`; -async function gqltadaCodegen(options: { - type: "HASURA" | "PORTAL" | "THE_GRAPH" | "THE_GRAPH_FALLBACK"; - env: DotEnv; - allowToFail?: boolean; -}) { - const accessToken = options.env.SETTLEMINT_ACCESS_TOKEN; - let templateName: string; - let output: string; - let clientName: string; - let fileName: string; - let gqlEndpoint: string | undefined = undefined; - let adminSecret: string | undefined = undefined; - - switch (options.type) { - case "HASURA": - gqlEndpoint = options.env.SETTLEMINT_HASURA_ENDPOINT; - adminSecret = options.env.SETTLEMINT_HASURA_ADMIN_SECRET; - templateName = "Hasura"; - clientName = "hasura"; - fileName = "hasura"; - output = `${fileName}-schema.graphql`; - break; - case "PORTAL": - gqlEndpoint = options.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT; - templateName = "Portal"; - clientName = "portal"; - fileName = "portal"; - output = `${fileName}-schema.graphql`; - break; - case "THE_GRAPH": - gqlEndpoint = options.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT; - templateName = "TheGraph"; - clientName = "theGraph"; - fileName = "the-graph"; - output = `${fileName}-schema.graphql`; - break; - case "THE_GRAPH_FALLBACK": - gqlEndpoint = options.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK; - templateName = "TheGraphFallback"; - clientName = "theGraphFallback"; - fileName = "the-graph-fallback"; - output = `${fileName}-schema.graphql`; - break; - } + await writeTemplate(clientTemplate, "/lib/settlemint", "hasura.ts"); +} +async function codegenPortal(env: DotEnv) { + const gqlEndpoint = env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT; if (!gqlEndpoint) { return; } + const accessToken = env.SETTLEMINT_ACCESS_TOKEN; + + await generateSchema({ + input: gqlEndpoint, + output: "portal-schema.graphql", + tsconfig: undefined, + headers: { + "x-auth-token": accessToken, + }, + }); + + const clientTemplate = `import { createServerPortalClient } from "@settlemint/sdk-portal"; +import type { introspection } from "../../portal-env.d.ts"; - const headers = { - ...(adminSecret && { "x-hasura-admin-secret": adminSecret }), - "x-auth-token": accessToken, - "Content-Type": "application/json", +export const { client: portalClient, graphql: portalGraphql } = createServerPortalClient<{ + introspection: introspection; + disableMasking: true; + scalars: { + DateTime: Date; + JSON: Record; }; +}>({ + instance: process.env.SETTLEMINT_PORTAL_GRAPHQL_ENDPOINT!, + accessToken: process.env.SETTLEMINT_ACCESS_TOKEN!, +});`; - try { - // Test the endpoint with a simple introspection query - const response = await fetch(gqlEndpoint, { - method: "POST", - headers, - body: JSON.stringify({ - query: ` - query { - __schema { - queryType { - name - } + await writeTemplate(clientTemplate, "/lib/settlemint", "portal.ts"); +} + +async function codegenTheGraph(env: DotEnv) { + const gqlEndpoint = env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT; + if (!gqlEndpoint) { + return; + } + const accessToken = env.SETTLEMINT_ACCESS_TOKEN; + + const response = await fetch(gqlEndpoint, { + method: "POST", + headers: { + "x-auth-token": accessToken, + }, + body: JSON.stringify({ + query: ` + query { + __schema { + queryType { + name } } - `, - }), - }); - - if (!response.ok) { - throw new Error(`Failed to fetch schema: ${response.statusText}`); - } - - const data = await response.json(); - if (data.errors) { - throw new Error(`GraphQL errors: ${JSON.stringify(data.errors)}`); - } - - await generateSchema({ - input: gqlEndpoint, - output, - tsconfig: undefined, - headers, - }); - - const clientTemplate = ` -import { createServer${templateName}Client } from "@settlemint/sdk-hasura"; -import type { introspection } from "../../${fileName}-env.d.ts"; - -export const { client: ${clientName}Client, graphql: ${clientName}Graphql } = createServer${templateName}Client<{ + } + `, + }), + }); + + if (!response.ok) { + return; + } + + const data = await response.json(); + if (data.errors) { + return; + } + + await generateSchema({ + input: gqlEndpoint, + output: "portal-schema.graphql", + tsconfig: undefined, + headers: { + "x-auth-token": accessToken, + }, + }); + + const clientTemplate = `import { createServerTheGraphClient } from "@settlemint/sdk-thegraph"; +import type { introspection } from "../../the-graph-env.d.ts"; + +export const { client: theGraphClient, graphql: theGraphGraphql } = createServerTheGraphClient<{ introspection: introspection; disableMasking: true; scalars: { @@ -207,24 +201,51 @@ export const { client: ${clientName}Client, graphql: ${clientName}Graphql } = cr JSON: Record; }; }>({ - instance: process.env.SETTLEMINT_${options.type}_ENDPOINT!, + instance: process.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT!, accessToken: process.env.SETTLEMINT_ACCESS_TOKEN!, - ${options.type === "HASURA" ? "adminSecret: process.env.SETTLEMINT_HASURA_ADMIN_SECRET!," : ""} -}); - `; - - const projectDir = await projectRoot(); - const codegenDir = join(projectDir, "/lib/settlemint"); - mkdirSync(codegenDir, { recursive: true }); - const filePath = join(codegenDir, fileName); - if (!existsSync(filePath)) { - writeFileSync(filePath, clientTemplate, "utf8"); - } - } catch (error) { - if (options.allowToFail) { - // ignore - } else { - throw error; - } +});`; + + await writeTemplate(clientTemplate, "/lib/settlemint", "the-graph.ts"); +} + +async function codegenTheGraphFallback(env: DotEnv) { + const gqlEndpoint = env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK; + if (!gqlEndpoint) { + return; } + const accessToken = env.SETTLEMINT_ACCESS_TOKEN; + + await generateSchema({ + input: gqlEndpoint, + output: "portal-schema.graphql", + tsconfig: undefined, + headers: { + "x-auth-token": accessToken, + }, + }); + + const clientTemplate = `import { createServerTheGraphClient } from "@settlemint/sdk-thegraph"; +import type { introspection } from "../../the-graph-fallback-env.d.ts"; + +export const { client: theGraphFallbackClient, graphql: theGraphFallbackGraphql } = createServerTheGraphClient<{ + introspection: introspection; + disableMasking: true; + scalars: { + DateTime: Date; + JSON: Record; + }; +}>({ + instance: process.env.SETTLEMINT_THEGRAPH_SUBGRAPH_ENDPOINT_FALLBACK!, + accessToken: process.env.SETTLEMINT_ACCESS_TOKEN!, +});`; + + await writeTemplate(clientTemplate, "/lib/settlemint", "the-graph-fallback.ts"); +} + +async function writeTemplate(template: string, directory: string, filename: string) { + const projectDir = await projectRoot(); + const codegenDir = join(projectDir, directory); + mkdirSync(codegenDir, { recursive: true }); + const filePath = join(codegenDir, filename); + writeFileSync(filePath, template, "utf8"); }