-
Notifications
You must be signed in to change notification settings - Fork 125
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
[api-codegen-preset] How to generate types for both Admin and Storefront #1058
Comments
Hey, thanks for raising this! Currently, const config: IGraphQLConfig = {
projects: {
default: shopifyApiProject({
apiType: ApiType.Admin,
apiVersion: LATEST_API_VERSION,
documents: ["./app/**/*.{js,ts,jsx,tsx}"],
outputDir: "./app/types",
}),
storefront: shopifyApiProject({
apiType: ApiType.Storefront,
apiVersion: LATEST_API_VERSION,
documents: ["./app/**/sf_*.{js,ts,jsx,tsx}"],
outputDir: "./app/types",
}),
},
}; There are a few caveats to consider when using
Hope this helps! |
Taking the neat idea of of
import { type ExpressionStatement, type TemplateLiteral } from "@babel/types";
import type { CodegenConfig } from "@graphql-codegen/cli";
import { ApiType, shopifyApiProject } from "@shopify/api-codegen-preset";
import type { IGraphQLConfig } from "graphql-config";
type Node = TemplateLiteral | ExpressionStatement;
type PluckConfig = CodegenConfig["pluckConfig"];
interface IProject extends ReturnType<typeof shopifyApiProject> {
extensions: {
codegen: {
pluckConfig: {
isGqlTemplateLiteral: (node: Node, options: PluckConfig) => boolean;
pluckStringFromFile: (
code: string,
node: Node,
options: PluckConfig,
) => string;
};
generates: Record<
string,
{ config?: { scalars?: unknown; minify?: boolean } }
>;
};
};
}
const OUTPUT_DIR = "./types";
const DOCUMENTS = [
// "../../apps/web/src/**/*.{js,ts,jsx,tsx}",
// "../api/src/**/*.{js,ts,jsx,tsx}",
"../api/src/routers/store/testing/create-test-outreach.ts",
];
const API_VERSION = "2025-01";
const SCALARS = {
ARN: "string",
BigInt: "string",
Color: "string",
Money: "string",
Date: "string",
FormattedString: "string",
DateTime: "string",
Decimal: "string",
HTML: "string",
ISO8601DateTime: "string",
URL: "string",
UnsignedInt64: "string",
JSON: "{ [key: string]: any }",
UtcOffset: "string",
};
const createIsGqlTemplateLiteral = (apiType: ApiType = ApiType.Admin) => {
return (
node: TemplateLiteral | ExpressionStatement,
options: CodegenConfig["pluckConfig"],
) => {
// Check for internal gql comment: const QUERY = `#graphql ...`
const regexPattern = `\\s*#graphql#${apiType}\\s*\\n`;
const regex = new RegExp(regexPattern, "i");
const hasInternalGqlComment =
node.type === "TemplateLiteral" &&
regex.test(node.quasis[0]?.value?.raw ?? "");
if (hasInternalGqlComment) return true;
// Check for leading gql comment: const QUERY = /* GraphQL */ `...`
const { leadingComments } = node;
const leadingComment = leadingComments?.[leadingComments?.length - 1];
const leadingCommentValue = leadingComment?.value?.trim().toLowerCase();
return leadingCommentValue === options?.gqlMagicComment;
};
};
const pluckStringFromFile = (
code: string,
{ start, end, leadingComments }: Node,
) => {
let gqlTemplate = code
// Slice quotes
.slice((start ?? 0) + 1, (end ?? 0) - 1)
// Annotate embedded expressions
// e.g. ${foo} -> #REQUIRED_VAR=foo
.replace(/\$\{([^}]*)\}/g, (_, m1) => "#REQUIRED_VAR=" + m1)
.split("\\`")
.join("`");
const chunkStart = leadingComments?.[0]?.start ?? start ?? 0;
const codeBeforeNode = code.slice(0, chunkStart);
const [, varName] = codeBeforeNode.match(/\s(\w+)\s*=\s*$/) ?? [];
if (varName) {
// Annotate with the name of the variable that stores this gql template.
// This is used to reconstruct the embedded expressions later.
gqlTemplate += "#VAR_NAME=" + varName;
}
return gqlTemplate;
};
const AdminGraphQLProject: IProject = shopifyApiProject({
apiType: ApiType.Admin,
apiVersion: API_VERSION,
documents: DOCUMENTS,
outputDir: OUTPUT_DIR,
}) as unknown as IProject;
AdminGraphQLProject.extensions.codegen.generates[
`${OUTPUT_DIR}/admin.types.d.ts`
]!.config = {
minify: true,
scalars: SCALARS,
};
AdminGraphQLProject.extensions.codegen.pluckConfig = {
isGqlTemplateLiteral: createIsGqlTemplateLiteral(ApiType.Admin),
pluckStringFromFile: pluckStringFromFile,
};
const StorefrontGraphQLProject: IProject = shopifyApiProject({
apiType: ApiType.Storefront,
apiVersion: API_VERSION,
documents: DOCUMENTS,
outputDir: OUTPUT_DIR,
}) as unknown as IProject;
StorefrontGraphQLProject.extensions.codegen.generates[
`${OUTPUT_DIR}/storefront.types.d.ts`
]!.config = {
minify: true,
scalars: SCALARS,
};
StorefrontGraphQLProject.extensions.codegen.pluckConfig = {
isGqlTemplateLiteral: createIsGqlTemplateLiteral(ApiType.Storefront),
pluckStringFromFile: pluckStringFromFile,
};
const config: IGraphQLConfig = {
schema: "https://shopify.dev/admin-graphql-direct-proxy/2025-01",
documents: DOCUMENTS,
projects: {
default: AdminGraphQLProject,
storefront: StorefrontGraphQLProject,
},
};
export default config; This allows me to follow the syntax of naming my queries accordingly e.g.
import "../types/admin.generated.d.ts";
import "../types/storefront.generated.d.ts";
const adminProducts = await admin.request(`#graphql#admin
query GetAdminProducts {
products(first: 10) {
edges {
node {
id
title
}
}
}
}
`);
const storefrontProducts = await storefront.request(`#graphql#storefront
query GetStorefrontProducts {
products(first: 10) {
edges {
node {
id
title
}
}
}
}
`); And here is my {
"scripts": {
"generate-admin-types": "graphql-codegen -c graphqlrc.config.ts -w",
"generate-storefront-types": "graphql-codegen -c graphqlrc.config.ts --project storefront -w",
"generate-types": "pnpm run generate-admin-types & pnpm run generate-storefront-types"
}
} |
Hi @Subraiz Thanks for the update! 👌 If you would like, you could create a PR to add this as an example set up in the readme. |
Overview
It seems like I can only generate either Storefront types or Admin types in a single
graphql-codegen
command. Is it possible to generate both types in a single command ? Thanks..graphqlrc.ts
The text was updated successfully, but these errors were encountered: