diff --git a/workspaces/leetcode-api/graphql-codegen.config.ts b/workspaces/leetcode-api/graphql-codegen.config.ts index 39083700..dbba93a7 100644 --- a/workspaces/leetcode-api/graphql-codegen.config.ts +++ b/workspaces/leetcode-api/graphql-codegen.config.ts @@ -1,6 +1,5 @@ import type { CodegenConfig } from "@graphql-codegen/cli"; import type { Types as GraphQLCodegen } from "@graphql-codegen/plugin-helpers"; -import immutableUpdate from "immutability-helper"; import { SCHEMA_PATCHED_FILE } from "./src/scripts/scrape-graphql-schema/constants.ts"; @@ -47,28 +46,6 @@ const headerPlugin: GraphQLCodegen.OutputConfig = { }, }; -const nearOperationFilePreset: GraphQLCodegen.ConfiguredOutput = { - preset: "near-operation-file", - presetConfig: { - // We enable `globalNamespace` below so that we don't import the base - // types through the preset, but let the specified plugins add the imports - // if necessary. This allows us to make sure the base types import is - // below the header, and it uses the ".ts" extension. - baseTypesPath: "", - - extension: ".generated.ts", - fileName: null as string | null, - }, - plugins: [headerPlugin], - config: { - ...commonTypeScriptPluginConfig, - - // Specified plugins will handle the imports and exports! - globalNamespace: true, - noExport: true, - }, -}; - const config: CodegenConfig = { schema: SCHEMA_PATCHED_FILE, documents: ["src/api/**/query.graphql"], @@ -81,45 +58,39 @@ const config: CodegenConfig = { config: commonTypeScriptPluginConfig, }, - // Generate a file for the query variables and result types, near each - // operation. - "src/api/**/queryTypes.generated.ts": immutableUpdate( - nearOperationFilePreset, - { - presetConfig: { fileName: { $set: "queryTypes" } }, - plugins: { - $push: [ - // Explicitly add the base types import, since the preset was - // tricked into not adding it. - { - add: { - content: - '\n\nimport type * as Types from "../../graphqlTypes.generated.ts";\n\n', - placement: "content", - }, - }, - - // Generate TypeScript operations types, overriding the - // `globalNamespace` which was set in the preset. - { "typescript-operations": { globalNamespace: false } }, - ], - }, + // Generate a small SDK for each operation using our custom plugin. + "src/api/**/fetchGraphQL.generated.ts": { + preset: "near-operation-file", + presetConfig: { + // We enable `globalNamespace` below so that we don't import the base + // types through the preset, but let the specified plugins add the + // imports if necessary. This allows us to make sure the base types + // import is below the header, and it uses the ".ts" extension. + baseTypesPath: "", - // Do export the types. - config: { noExport: { $set: false } }, + fileName: "fetchGraphQL", + extension: ".generated.ts", }, - ), + plugins: [ + headerPlugin, - // Generate a small SDK for each operation using our custom plugin. - "src/api/**/fetchGraphQL.generated.ts": immutableUpdate( - nearOperationFilePreset, - { - presetConfig: { fileName: { $set: "fetchGraphQL" } }, - plugins: { - $push: ["./src/scripts/codegen/graphqlCodegenPlugin.ts"], + { + "typescript-operations": { + // Override the `globalNamespace` which was set in the preset. + globalNamespace: false, + }, }, + + "./src/scripts/codegen/graphqlCodegenPlugin.ts", + ], + config: { + ...commonTypeScriptPluginConfig, + + // Specified plugins will handle the imports and exports! + globalNamespace: true, + noExport: true, }, - ), + }, }, hooks: { diff --git a/workspaces/leetcode-api/package.json b/workspaces/leetcode-api/package.json index 16a5d2a0..426b98c7 100644 --- a/workspaces/leetcode-api/package.json +++ b/workspaces/leetcode-api/package.json @@ -46,7 +46,6 @@ "jest": "29.7.0", "prettier": "3.3.3", "ts-jest": "29.2.5", - "ts-to-zod": "3.13.0", "tsx": "4.19.1", "typescript": "5.6.2" } diff --git a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts index 46f49568..832c7fd3 100644 --- a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/fetchGraphQL.generated.ts @@ -4,10 +4,23 @@ import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type { - ActiveDailyCodingChallengeQuestionQuery as OriginalQueryResult, - ActiveDailyCodingChallengeQuestionQueryVariables as OriginalQueryVariables, -} from "./queryTypes.generated.ts"; +import type * as Types from "../../graphqlTypes.generated.ts"; + +type ActiveDailyCodingChallengeQuestionQueryVariables = Types.Exact<{ + [key: string]: never; +}>; + +type ActiveDailyCodingChallengeQuestionQuery = { + activeDailyCodingChallengeQuestion: { + date: string; + question: { + difficulty: string; + questionFrontendId: string; + title: string; + titleSlug: string; + }; + }; +}; export const QUERY = "query{activeDailyCodingChallengeQuestion{date question{difficulty questionFrontendId title titleSlug}}}"; @@ -25,14 +38,14 @@ export const queryResultZodType = z.object({ }); export type QueryResult = z.infer; -export type QueryVariables = OriginalQueryVariables; +export type QueryVariables = ActiveDailyCodingChallengeQuestionQueryVariables; export async function fetchGraphQL(): Promise { const untrustedData = await getGraphQLClient().request(QUERY); // The type annotation serves as a TypeScript assert that the generated // Zod type is compatible with the types generated by GraphQL Codegen. - const validatedData: OriginalQueryResult = + const validatedData: ActiveDailyCodingChallengeQuestionQuery & QueryResult = queryResultZodType.parse(untrustedData); return validatedData; diff --git a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts deleted file mode 100644 index 953464f6..00000000 --- a/workspaces/leetcode-api/src/api/active-daily-coding-challenge-question/queryTypes.generated.ts +++ /dev/null @@ -1,20 +0,0 @@ -// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! -// Instead, update the generation process or inputs and run `yarn codegen`. - -import type * as Types from "../../graphqlTypes.generated.ts"; - -export type ActiveDailyCodingChallengeQuestionQueryVariables = Types.Exact<{ - [key: string]: never; -}>; - -export type ActiveDailyCodingChallengeQuestionQuery = { - activeDailyCodingChallengeQuestion: { - date: string; - question: { - difficulty: string; - questionFrontendId: string; - title: string; - titleSlug: string; - }; - }; -}; diff --git a/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts index 44459a54..434bc0e0 100644 --- a/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/question-list/fetchGraphQL.generated.ts @@ -4,36 +4,50 @@ import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type { - QuestionListQuery as OriginalQueryResult, - QuestionListQueryVariables as OriginalQueryVariables, -} from "./queryTypes.generated.ts"; +import type * as Types from "../../graphqlTypes.generated.ts"; + +type QuestionListQueryVariables = Types.Exact<{ + categorySlug: Types.Scalars["String"]["input"]; + limit?: Types.InputMaybe; + skip?: Types.InputMaybe; + filters: Types.QuestionListFilterInput; +}>; + +type QuestionListQuery = { + questionList: { + totalNum: number; + data: Array<{ + difficulty: string; + isPaidOnly: boolean; + questionFrontendId: string; + title: string; + titleSlug: string; + challengeQuestionsV2: Array<{ date: string }>; + }>; + }; +}; export const QUERY = "query($categorySlug:String!,$limit:Int,$skip:Int,$filters:QuestionListFilterInput!){questionList(categorySlug:$categorySlug limit:$limit skip:$skip filters:$filters){data{challengeQuestionsV2{date}difficulty isPaidOnly questionFrontendId title titleSlug}totalNum}}"; export const queryResultZodType = z.object({ questionList: z.object({ - totalNum: z.number(), data: z.array( z.object({ + challengeQuestionsV2: z.array(z.object({ date: z.string() })), difficulty: z.string(), isPaidOnly: z.boolean(), questionFrontendId: z.string(), title: z.string(), titleSlug: z.string(), - challengeQuestionsV2: z.array( - z.object({ - date: z.string(), - }), - ), }), ), + totalNum: z.number().int().nonnegative(), }), }); export type QueryResult = z.infer; -export type QueryVariables = OriginalQueryVariables; +export type QueryVariables = QuestionListQueryVariables; export async function fetchGraphQL( variables: QueryVariables, @@ -42,7 +56,7 @@ export async function fetchGraphQL( // The type annotation serves as a TypeScript assert that the generated // Zod type is compatible with the types generated by GraphQL Codegen. - const validatedData: OriginalQueryResult = + const validatedData: QuestionListQuery & QueryResult = queryResultZodType.parse(untrustedData); return validatedData; diff --git a/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts deleted file mode 100644 index d10cc142..00000000 --- a/workspaces/leetcode-api/src/api/question-list/queryTypes.generated.ts +++ /dev/null @@ -1,25 +0,0 @@ -// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! -// Instead, update the generation process or inputs and run `yarn codegen`. - -import type * as Types from "../../graphqlTypes.generated.ts"; - -export type QuestionListQueryVariables = Types.Exact<{ - categorySlug: Types.Scalars["String"]["input"]; - limit?: Types.InputMaybe; - skip?: Types.InputMaybe; - filters: Types.QuestionListFilterInput; -}>; - -export type QuestionListQuery = { - questionList: { - totalNum: number; - data: Array<{ - difficulty: string; - isPaidOnly: boolean; - questionFrontendId: string; - title: string; - titleSlug: string; - challengeQuestionsV2: Array<{ date: string }>; - }>; - }; -}; diff --git a/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts index b27669f2..16bbb61b 100644 --- a/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/recent-ac-submission-list/fetchGraphQL.generated.ts @@ -4,10 +4,21 @@ import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type { - RecentAcSubmissionListQuery as OriginalQueryResult, - RecentAcSubmissionListQueryVariables as OriginalQueryVariables, -} from "./queryTypes.generated.ts"; +import type * as Types from "../../graphqlTypes.generated.ts"; + +type RecentAcSubmissionListQueryVariables = Types.Exact<{ + username: Types.Scalars["String"]["input"]; + limit: Types.Scalars["Int"]["input"]; +}>; + +type RecentAcSubmissionListQuery = { + recentAcSubmissionList: Array<{ + id: string; + title: string; + titleSlug: string; + timestamp: string; + }>; +}; export const QUERY = "query($username:String!,$limit:Int!){recentAcSubmissionList(username:$username,limit:$limit){id title titleSlug timestamp}}"; @@ -24,7 +35,7 @@ export const queryResultZodType = z.object({ }); export type QueryResult = z.infer; -export type QueryVariables = OriginalQueryVariables; +export type QueryVariables = RecentAcSubmissionListQueryVariables; export async function fetchGraphQL( variables: QueryVariables, @@ -33,7 +44,7 @@ export async function fetchGraphQL( // The type annotation serves as a TypeScript assert that the generated // Zod type is compatible with the types generated by GraphQL Codegen. - const validatedData: OriginalQueryResult = + const validatedData: RecentAcSubmissionListQuery & QueryResult = queryResultZodType.parse(untrustedData); return validatedData; diff --git a/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts deleted file mode 100644 index f1f774d8..00000000 --- a/workspaces/leetcode-api/src/api/recent-ac-submission-list/queryTypes.generated.ts +++ /dev/null @@ -1,18 +0,0 @@ -// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! -// Instead, update the generation process or inputs and run `yarn codegen`. - -import type * as Types from "../../graphqlTypes.generated.ts"; - -export type RecentAcSubmissionListQueryVariables = Types.Exact<{ - username: Types.Scalars["String"]["input"]; - limit: Types.Scalars["Int"]["input"]; -}>; - -export type RecentAcSubmissionListQuery = { - recentAcSubmissionList: Array<{ - id: string; - title: string; - titleSlug: string; - timestamp: string; - }>; -}; diff --git a/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts b/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts index 0752204f..d4f454f1 100644 --- a/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts +++ b/workspaces/leetcode-api/src/api/topic/fetchGraphQL.generated.ts @@ -4,10 +4,19 @@ import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; -import type { - TopicQuery as OriginalQueryResult, - TopicQueryVariables as OriginalQueryVariables, -} from "./queryTypes.generated.ts"; +import type * as Types from "../../graphqlTypes.generated.ts"; + +type TopicQueryVariables = Types.Exact<{ + topicId: Types.Scalars["Int"]["input"]; +}>; + +type TopicQuery = { + topic?: { + title: string; + solutionTags: Array<{ slug: string } | null>; + post: { content: string }; + } | null; +}; export const QUERY = "query($topicId:Int!){topic(id:$topicId){title solutionTags{slug}post{content}}}"; @@ -16,23 +25,15 @@ export const queryResultZodType = z.object({ topic: z .object({ title: z.string(), - solutionTags: z.array( - z - .object({ - slug: z.string(), - }) - .nullable(), - ), - post: z.object({ - content: z.string(), - }), + solutionTags: z.array(z.object({ slug: z.string() }).nullable()), + post: z.object({ content: z.string() }), }) - .optional() - .nullable(), + .nullable() + .optional(), }); export type QueryResult = z.infer; -export type QueryVariables = OriginalQueryVariables; +export type QueryVariables = TopicQueryVariables; export async function fetchGraphQL( variables: QueryVariables, @@ -41,7 +42,7 @@ export async function fetchGraphQL( // The type annotation serves as a TypeScript assert that the generated // Zod type is compatible with the types generated by GraphQL Codegen. - const validatedData: OriginalQueryResult = + const validatedData: TopicQuery & QueryResult = queryResultZodType.parse(untrustedData); return validatedData; diff --git a/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts b/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts deleted file mode 100644 index 10923f2f..00000000 --- a/workspaces/leetcode-api/src/api/topic/queryTypes.generated.ts +++ /dev/null @@ -1,16 +0,0 @@ -// THIS FILE IS GENERATED! DO NOT MODIFY IT MANUALLY!! -// Instead, update the generation process or inputs and run `yarn codegen`. - -import type * as Types from "../../graphqlTypes.generated.ts"; - -export type TopicQueryVariables = Types.Exact<{ - topicId: Types.Scalars["Int"]["input"]; -}>; - -export type TopicQuery = { - topic?: { - title: string; - solutionTags: Array<{ slug: string } | null>; - post: { content: string }; - } | null; -}; diff --git a/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts b/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts deleted file mode 100644 index ff175d85..00000000 --- a/workspaces/leetcode-api/src/scripts/codegen/createTypeScriptSourceFile.ts +++ /dev/null @@ -1,8 +0,0 @@ -import { createSourceFile, ScriptTarget, type SourceFile } from "typescript"; - -export function createTypeScriptSourceFile( - sourceText: string = "", -): SourceFile { - // TODO: share this constant with the adventure pack? - return createSourceFile("not/a/real/file", sourceText, ScriptTarget.ESNext); -} diff --git a/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts b/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts deleted file mode 100644 index dbaeedc7..00000000 --- a/workspaces/leetcode-api/src/scripts/codegen/generateQueryResultZodType.ts +++ /dev/null @@ -1,28 +0,0 @@ -import { generateZodSchemaVariableStatement } from "ts-to-zod"; -import { isTypeAliasDeclaration } from "typescript"; - -import { only } from "@code-chronicles/util/only"; - -import { stringifyTypeScriptNode } from "./stringifyTypeScriptNode.ts"; -import { readTypeScriptSourceFile } from "./readTypeScriptSourceFile.ts"; - -export async function generateQueryResultZodType( - filePath: string, -): Promise { - // TODO: Unfortunately this ends up reading the _old_ version of this file, not the one after the codegen - const sourceFile = await readTypeScriptSourceFile(filePath); - const queryNode = only( - sourceFile.statements - .filter(isTypeAliasDeclaration) - .filter((statement) => statement.name.text.endsWith("Query")), - ); - - const { statement } = generateZodSchemaVariableStatement({ - node: queryNode, - sourceFile, - varName: "queryResultZodType", - customJSDocFormatTypes: {}, - }); - - return stringifyTypeScriptNode(statement); -} diff --git a/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts b/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts index 90cac6ef..6ca0f0e5 100644 --- a/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts +++ b/workspaces/leetcode-api/src/scripts/codegen/graphqlCodegenPlugin.ts @@ -1,7 +1,6 @@ -import path from "node:path"; - import type { PluginFunction } from "@graphql-codegen/plugin-helpers"; import graphqlQueryCompress from "graphql-query-compress"; +import { Kind, OperationTypeNode } from "graphql"; import invariant from "invariant"; import nullthrows from "nullthrows"; import { z } from "zod"; @@ -9,7 +8,7 @@ import { z } from "zod"; import { only } from "@code-chronicles/util/only"; import { spliceString } from "@code-chronicles/util/spliceString"; -import { generateQueryResultZodType } from "./generateQueryResultZodType.ts"; +import { graphqlToZod } from "./graphqlToZod.ts"; const nonNegativeIntZodType = z.number().int().nonnegative(); @@ -18,17 +17,14 @@ const operationNameZodType = z.object({ loc: z.object({ start: nonNegativeIntZodType, end: nonNegativeIntZodType }), }); -export const plugin: PluginFunction<{}> = async function plugin( - _schema, - documents, -) { +export const plugin: PluginFunction<{}> = function plugin(schema, documents) { // Encode some assumptions as invariants, namely that there is a single query // operation in the file. - const { document, rawSDL: unminifiedGraphQL, location } = only(documents); + const { document, rawSDL: unminifiedGraphQL } = only(documents); const definition = only(nullthrows(document).definitions); invariant( - definition.kind === "OperationDefinition" && - definition.operation === "query", + definition.kind === Kind.OPERATION_DEFINITION && + definition.operation === OperationTypeNode.QUERY, "Expected a query!", ); @@ -36,14 +32,6 @@ export const plugin: PluginFunction<{}> = async function plugin( // TODO: should we call it a Zod schema instead? - // Generate a Zod type to parse the result! - const queryTypesFile = path.join( - path.dirname(nullthrows(location)), - "queryTypes.generated.ts", - ); - const queryResultZodTypeCode = - await generateQueryResultZodType(queryTypesFile); - // Extract the operation name from the definition, as well as information // about its location in the raw, unminified GraphQL. const { @@ -71,27 +59,24 @@ export const plugin: PluginFunction<{}> = async function plugin( import { z } from "zod"; import { getGraphQLClient } from "../../getGraphQLClient.ts"; - import type { - ${operationName}Query as OriginalQueryResult, - ${operationName}QueryVariables as OriginalQueryVariables, - } from "./queryTypes.generated.ts"; + import type * as Types from "../../graphqlTypes.generated.ts"; `, ], append: [ ` export const QUERY = ${JSON.stringify(minifiedGraphQL)}; - ${queryResultZodTypeCode} + export const queryResultZodType = ${graphqlToZod(nullthrows(schema.getQueryType()), definition.selectionSet)}; export type QueryResult = z.infer; - export type QueryVariables = OriginalQueryVariables; + export type QueryVariables = ${operationName}QueryVariables; export async function fetchGraphQL(${hasVariables ? "variables: QueryVariables" : ""}): Promise { const untrustedData = await getGraphQLClient().request(QUERY${hasVariables ? ", variables" : ""}); // The type annotation serves as a TypeScript assert that the generated // Zod type is compatible with the types generated by GraphQL Codegen. - const validatedData: OriginalQueryResult = queryResultZodType.parse(untrustedData); + const validatedData: ${operationName}Query & QueryResult = queryResultZodType.parse(untrustedData); return validatedData; } diff --git a/workspaces/leetcode-api/src/scripts/codegen/graphqlToZod.ts b/workspaces/leetcode-api/src/scripts/codegen/graphqlToZod.ts new file mode 100644 index 00000000..02290d17 --- /dev/null +++ b/workspaces/leetcode-api/src/scripts/codegen/graphqlToZod.ts @@ -0,0 +1,148 @@ +import invariant from "invariant"; +import nullthrows from "nullthrows"; + +import { + GraphQLList, + GraphQLNonNull, + GraphQLObjectType, + GraphQLScalarType, + Kind, + type ConstDirectiveNode, + type GraphQLNamedType, + type SelectionSetNode, +} from "graphql"; + +// TODO: get some of these directives added to the schema +type DirectivesConfig = { + nonnegative: boolean; +}; + +function parseDirectives( + directives: readonly ConstDirectiveNode[], +): DirectivesConfig { + const res = { nonnegative: false }; + for (const directive of directives) { + switch (directive.name.value) { + case "nonnegative": { + invariant(!directive.arguments?.length, "No arguments allowed!"); + res.nonnegative = true; + break; + } + default: { + throw new Error("Unsupported directive " + directive.name.value); + } + } + } + + return res; +} + +class ZodOutput { + constructor( + public text: string, + public isNullable: boolean, + ) {} + + setIsNullable(isNullable: boolean): this { + this.isNullable = isNullable; + return this; + } + + stringify(canHaveOptionals: boolean = true): string { + return this.isNullable + ? [this.text, ".nullable()", canHaveOptionals ? ".optional()" : ""].join( + "", + ) + : this.text; + } +} + +function generateZod( + currentType: GraphQLNamedType, + selectionSet: SelectionSetNode | undefined, + directives: readonly ConstDirectiveNode[] = [], +): ZodOutput { + if (currentType instanceof GraphQLScalarType) { + switch (currentType.name) { + case "Boolean": { + invariant(directives.length === 0, "Directives not supported here."); + return new ZodOutput("z.boolean()", true); + } + case "Int": { + const directiveConfig = parseDirectives(directives); + return new ZodOutput( + [ + "z.number().int()", + directiveConfig.nonnegative ? ".nonnegative()" : "", + ].join(""), + true, + ); + } + case "Date": + case "DateTime": + case "ID": + case "String": { + invariant(directives.length === 0, "Directives not supported here."); + return new ZodOutput("z.string()", true); + } + default: { + throw new Error("Unsupported scalar type: " + currentType.name); + } + } + } + + if (currentType instanceof GraphQLObjectType) { + invariant(directives.length === 0, "Directives not supported here."); + + const selections = nullthrows(selectionSet).selections.map((s) => { + invariant(s.kind === Kind.FIELD, "Expected direct fields!"); + + const processStack: ((prev: ZodOutput) => ZodOutput)[] = []; + + const field = currentType.getFields()[s.name.value]; + let fieldType = field.type; + + while (true) { + if (fieldType instanceof GraphQLList) { + processStack.push( + (prev) => new ZodOutput(`z.array(${prev.stringify(false)})`, true), + ); + fieldType = fieldType.ofType; + continue; + } + + if (fieldType instanceof GraphQLNonNull) { + processStack.push((prev) => prev.setIsNullable(false)); + fieldType = fieldType.ofType; + continue; + } + + return ( + JSON.stringify(s.name.value) + + ": " + + processStack + .reduceRight( + (acc, fn) => fn(acc), + generateZod( + fieldType, + s.selectionSet, + field.astNode?.directives ?? [], + ), + ) + .stringify() + ); + } + }); + + return new ZodOutput(`z.object({${selections.join(",")}})`, true); + } + + throw new Error("Unsupported type: " + currentType.name); +} + +export function graphqlToZod( + queryType: GraphQLObjectType, + selectionSet: SelectionSetNode, +): string { + return generateZod(queryType, selectionSet).setIsNullable(false).stringify(); +} diff --git a/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts b/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts deleted file mode 100644 index 0d6fe01b..00000000 --- a/workspaces/leetcode-api/src/scripts/codegen/readTypeScriptSourceFile.ts +++ /dev/null @@ -1,14 +0,0 @@ -import { readFile } from "node:fs/promises"; - -import type { SourceFile } from "typescript"; - -import { createTypeScriptSourceFile } from "./createTypeScriptSourceFile.ts"; - -export async function readTypeScriptSourceFile( - path: string, -): Promise { - const sourceText = await readFile(path, { encoding: "utf8" }); - return createTypeScriptSourceFile(sourceText); -} - -// TODO: merge typescript and ts-morph stuff diff --git a/workspaces/leetcode-api/src/scripts/codegen/stringifyTypeScriptNode.ts b/workspaces/leetcode-api/src/scripts/codegen/stringifyTypeScriptNode.ts deleted file mode 100644 index 970b8b49..00000000 --- a/workspaces/leetcode-api/src/scripts/codegen/stringifyTypeScriptNode.ts +++ /dev/null @@ -1,11 +0,0 @@ -import { createPrinter, EmitHint, type Statement } from "typescript"; - -import { createTypeScriptSourceFile } from "./createTypeScriptSourceFile.ts"; - -export function stringifyTypeScriptNode(statement: Statement): string { - return createPrinter().printNode( - EmitHint.Unspecified, - statement, - createTypeScriptSourceFile(""), - ); -} diff --git a/yarn.lock b/yarn.lock index c86d89cb..1aa8c5d1 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1029,7 +1029,6 @@ __metadata: nullthrows: "patch:nullthrows@npm%3A1.1.1#~/.yarn/patches/nullthrows-npm-1.1.1-3d1f817134.patch" prettier: "npm:3.3.3" ts-jest: "npm:29.2.5" - ts-to-zod: "npm:3.13.0" tsx: "npm:4.19.1" typescript: "npm:5.6.2" zod: "npm:3.23.8" @@ -2627,32 +2626,6 @@ __metadata: languageName: node linkType: hard -"@oclif/core@npm:>=3.26.0": - version: 4.0.27 - resolution: "@oclif/core@npm:4.0.27" - dependencies: - ansi-escapes: "npm:^4.3.2" - ansis: "npm:^3.3.2" - clean-stack: "npm:^3.0.1" - cli-spinners: "npm:^2.9.2" - debug: "npm:^4.3.7" - ejs: "npm:^3.1.10" - get-package-type: "npm:^0.1.0" - globby: "npm:^11.1.0" - indent-string: "npm:^4.0.0" - is-wsl: "npm:^3" - lilconfig: "npm:^3.1.2" - minimatch: "npm:^9.0.5" - semver: "npm:^7.6.3" - string-width: "npm:^4.2.3" - supports-color: "npm:^8" - widest-line: "npm:^3.1.0" - wordwrap: "npm:^1.0.0" - wrap-ansi: "npm:^7.0.0" - checksum: 10c0/ee358d079785a23f88d816406b74eaa8bf00aba68b90307a5a46803bb96c7483ca3fd72f223cff1e2bfd0ca8a1fb1bdd4a76b165dad3e411a7dc19663cea960e - languageName: node - linkType: hard - "@pkgjs/parseargs@npm:^0.11.0": version: 0.11.0 resolution: "@pkgjs/parseargs@npm:0.11.0" @@ -3109,17 +3082,6 @@ __metadata: languageName: node linkType: hard -"@typescript/vfs@npm:^1.5.0": - version: 1.6.0 - resolution: "@typescript/vfs@npm:1.6.0" - dependencies: - debug: "npm:^4.1.1" - peerDependencies: - typescript: "*" - checksum: 10c0/35e17d92f0d4f33c4be12fc4468196788794bc2edc1a371f1023c42314f6d1e0e851f07b45732a634ef750e61e2ef8769e8ab4f6a6c511cea8da397fa87852ff - languageName: node - linkType: hard - "@vladfrangu/async_event_emitter@npm:^2.2.4, @vladfrangu/async_event_emitter@npm:^2.4.6": version: 2.4.6 resolution: "@vladfrangu/async_event_emitter@npm:2.4.6" @@ -3442,7 +3404,7 @@ __metadata: languageName: node linkType: hard -"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0, ansi-escapes@npm:^4.3.2": +"ansi-escapes@npm:^4.2.1, ansi-escapes@npm:^4.3.0": version: 4.3.2 resolution: "ansi-escapes@npm:4.3.2" dependencies: @@ -3506,13 +3468,6 @@ __metadata: languageName: node linkType: hard -"ansis@npm:^3.3.2": - version: 3.3.2 - resolution: "ansis@npm:3.3.2" - checksum: 10c0/7b5bd3d28e5fa12dbd2268e984b292540f863bb113fbaa78be93e512417df014e547279ecec264b39948364e13f245424a0bc911802d68def185545d2290d11c - languageName: node - linkType: hard - "anymatch@npm:^3.0.3, anymatch@npm:~3.1.2": version: 3.1.3 resolution: "anymatch@npm:3.1.3" @@ -3952,7 +3907,7 @@ __metadata: languageName: node linkType: hard -"callsites@npm:^3.0.0, callsites@npm:^3.1.0": +"callsites@npm:^3.0.0": version: 3.1.0 resolution: "callsites@npm:3.1.0" checksum: 10c0/fff92277400eb06c3079f9e74f3af120db9f8ea03bad0e84d9aede54bbe2d44a56cccb5f6cf12211f93f52306df87077ecec5b712794c5a9b5dac6d615a3f301 @@ -4001,13 +3956,6 @@ __metadata: languageName: node linkType: hard -"case@npm:^1.6.3": - version: 1.6.3 - resolution: "case@npm:1.6.3" - checksum: 10c0/43fcbb1dff1c4add94dd2bc98bd923d6616f10bff6959adf686d192c3db7d7ced35410761e1ac94cc4a1f5c41c86406ad79d390805539e421e8a77e553f67223 - languageName: node - linkType: hard - "chalk@npm:^2.4.2": version: 2.4.2 resolution: "chalk@npm:2.4.2" @@ -4159,7 +4107,7 @@ __metadata: languageName: node linkType: hard -"chokidar@npm:^3.5.1, chokidar@npm:^3.5.3": +"chokidar@npm:^3.5.3": version: 3.6.0 resolution: "chokidar@npm:3.6.0" dependencies: @@ -4213,15 +4161,6 @@ __metadata: languageName: node linkType: hard -"clean-stack@npm:^3.0.1": - version: 3.0.1 - resolution: "clean-stack@npm:3.0.1" - dependencies: - escape-string-regexp: "npm:4.0.0" - checksum: 10c0/4ea5c03bdf78e8afb2592f34c1b5832d0c7858d37d8b0d40fba9d61a103508fa3bb527d39a99469019083e58e05d1ad54447e04217d5d36987e97182adab0e03 - languageName: node - linkType: hard - "cli-cursor@npm:^3.1.0": version: 3.1.0 resolution: "cli-cursor@npm:3.1.0" @@ -4240,7 +4179,7 @@ __metadata: languageName: node linkType: hard -"cli-spinners@npm:^2.5.0, cli-spinners@npm:^2.9.2": +"cli-spinners@npm:^2.5.0": version: 2.9.2 resolution: "cli-spinners@npm:2.9.2" checksum: 10c0/907a1c227ddf0d7a101e7ab8b300affc742ead4b4ebe920a5bf1bc6d45dce2958fcd195eb28fa25275062fe6fa9b109b93b63bc8033396ed3bcb50297008b3a3 @@ -4580,7 +4519,7 @@ __metadata: languageName: node linkType: hard -"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.2.0, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:^4.3.7, debug@npm:~4.3.6": +"debug@npm:4, debug@npm:^4.0.0, debug@npm:^4.1.0, debug@npm:^4.1.1, debug@npm:^4.3.1, debug@npm:^4.3.2, debug@npm:^4.3.4, debug@npm:^4.3.5, debug@npm:~4.3.6": version: 4.3.7 resolution: "debug@npm:4.3.7" dependencies: @@ -5121,13 +5060,6 @@ __metadata: languageName: node linkType: hard -"escape-string-regexp@npm:4.0.0, escape-string-regexp@npm:^4.0.0": - version: 4.0.0 - resolution: "escape-string-regexp@npm:4.0.0" - checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 - languageName: node - linkType: hard - "escape-string-regexp@npm:^1.0.5": version: 1.0.5 resolution: "escape-string-regexp@npm:1.0.5" @@ -5142,6 +5074,13 @@ __metadata: languageName: node linkType: hard +"escape-string-regexp@npm:^4.0.0": + version: 4.0.0 + resolution: "escape-string-regexp@npm:4.0.0" + checksum: 10c0/9497d4dd307d845bd7f75180d8188bb17ea8c151c1edbf6b6717c100e104d629dc2dfb687686181b0f4b7d732c7dfdc4d5e7a8ff72de1b0ca283a75bbb3a9cd9 + languageName: node + linkType: hard + "eslint-import-resolver-node@npm:^0.3.9": version: 0.3.9 resolution: "eslint-import-resolver-node@npm:0.3.9" @@ -5341,13 +5280,6 @@ __metadata: languageName: node linkType: hard -"esm@npm:^3.2.25": - version: 3.2.25 - resolution: "esm@npm:3.2.25" - checksum: 10c0/8e60e8075506a7ce28681c30c8f54623fe18a251c364cd481d86719fc77f58aa055b293d80632d9686d5408aaf865ffa434897dc9fd9153c8b3f469fad23f094 - languageName: node - linkType: hard - "espree@npm:^10.0.1, espree@npm:^10.2.0": version: 10.2.0 resolution: "espree@npm:10.2.0" @@ -5768,17 +5700,6 @@ __metadata: languageName: node linkType: hard -"fs-extra@npm:^11.1.1": - version: 11.2.0 - resolution: "fs-extra@npm:11.2.0" - dependencies: - graceful-fs: "npm:^4.2.0" - jsonfile: "npm:^6.0.1" - universalify: "npm:^2.0.0" - checksum: 10c0/d77a9a9efe60532d2e790e938c81a02c1b24904ef7a3efb3990b835514465ba720e99a6ea56fd5e2db53b4695319b644d76d5a0e9988a2beef80aa7b1da63398 - languageName: node - linkType: hard - "fs-minipass@npm:^2.0.0": version: 2.1.0 resolution: "fs-minipass@npm:2.1.0" @@ -6017,7 +5938,7 @@ __metadata: languageName: node linkType: hard -"globby@npm:^11.0.3, globby@npm:^11.1.0": +"globby@npm:^11.0.3": version: 11.1.0 resolution: "globby@npm:11.1.0" dependencies: @@ -6421,7 +6342,7 @@ __metadata: languageName: node linkType: hard -"inquirer@npm:^8.0.0, inquirer@npm:^8.2.0": +"inquirer@npm:^8.0.0": version: 8.2.6 resolution: "inquirer@npm:8.2.6" dependencies: @@ -6603,15 +6524,6 @@ __metadata: languageName: node linkType: hard -"is-docker@npm:^3.0.0": - version: 3.0.0 - resolution: "is-docker@npm:3.0.0" - bin: - is-docker: cli.js - checksum: 10c0/d2c4f8e6d3e34df75a5defd44991b6068afad4835bb783b902fa12d13ebdb8f41b2a199dcb0b5ed2cb78bfee9e4c0bbdb69c2d9646f4106464674d3e697a5856 - languageName: node - linkType: hard - "is-extglob@npm:^2.1.1": version: 2.1.1 resolution: "is-extglob@npm:2.1.1" @@ -6665,17 +6577,6 @@ __metadata: languageName: node linkType: hard -"is-inside-container@npm:^1.0.0": - version: 1.0.0 - resolution: "is-inside-container@npm:1.0.0" - dependencies: - is-docker: "npm:^3.0.0" - bin: - is-inside-container: cli.js - checksum: 10c0/a8efb0e84f6197e6ff5c64c52890fa9acb49b7b74fed4da7c95383965da6f0fa592b4dbd5e38a79f87fc108196937acdbcd758fcefc9b140e479b39ce1fcd1cd - languageName: node - linkType: hard - "is-interactive@npm:^1.0.0": version: 1.0.0 resolution: "is-interactive@npm:1.0.0" @@ -6722,13 +6623,6 @@ __metadata: languageName: node linkType: hard -"is-observable@npm:^2.1.0": - version: 2.1.0 - resolution: "is-observable@npm:2.1.0" - checksum: 10c0/f6ae9e136f66ad59c4faa4661112c389b398461cdeb0ef5bc3c505989469b77b2ba4602e2abc54a635d65f616eec9b5a40cd7d2c1f96b2cc4748b56635eba1c6 - languageName: node - linkType: hard - "is-plain-obj@npm:^4.0.0": version: 4.1.0 resolution: "is-plain-obj@npm:4.1.0" @@ -6855,15 +6749,6 @@ __metadata: languageName: node linkType: hard -"is-wsl@npm:^3": - version: 3.1.0 - resolution: "is-wsl@npm:3.1.0" - dependencies: - is-inside-container: "npm:^1.0.0" - checksum: 10c0/d3317c11995690a32c362100225e22ba793678fe8732660c6de511ae71a0ff05b06980cf21f98a6bf40d7be0e9e9506f859abe00a1118287d63e53d0a3d06947 - languageName: node - linkType: hard - "isarray@npm:^2.0.5": version: 2.0.5 resolution: "isarray@npm:2.0.5" @@ -7627,7 +7512,7 @@ __metadata: languageName: node linkType: hard -"lilconfig@npm:^3.1.2, lilconfig@npm:~3.1.2": +"lilconfig@npm:~3.1.2": version: 3.1.2 resolution: "lilconfig@npm:3.1.2" checksum: 10c0/f059630b1a9bddaeba83059db00c672b64dc14074e9f232adce32b38ca1b5686ab737eb665c5ba3c32f147f0002b4bee7311ad0386a9b98547b5623e87071fbe @@ -8677,13 +8562,6 @@ __metadata: languageName: node linkType: hard -"observable-fns@npm:^0.6.1": - version: 0.6.1 - resolution: "observable-fns@npm:0.6.1" - checksum: 10c0/bf25d5b3e4040233e886800c48b347361d9c7a1179f345590e671c2dd5ea9b4447bd5037f8ed40b2bb6fd7e205f0c5450eff15f48efdf91b3dec027007cf2834 - languageName: node - linkType: hard - "once@npm:^1.3.0": version: 1.4.0 resolution: "once@npm:1.4.0" @@ -8734,7 +8612,7 @@ __metadata: languageName: node linkType: hard -"ora@npm:^5.4.0, ora@npm:^5.4.1": +"ora@npm:^5.4.1": version: 5.4.1 resolution: "ora@npm:5.4.1" dependencies: @@ -9039,15 +8917,6 @@ __metadata: languageName: node linkType: hard -"prettier@npm:3.0.3": - version: 3.0.3 - resolution: "prettier@npm:3.0.3" - bin: - prettier: bin/prettier.cjs - checksum: 10c0/f950887bc03c5b970d8c6dd129364acfbbc61e7b46aec5d5ce17f4adf6404e2ef43072c98b51c4786e0eaca949b307d362a773fd47502862d754b5a328fa2b26 - languageName: node - linkType: hard - "prettier@npm:3.2.5": version: 3.2.5 resolution: "prettier@npm:3.2.5" @@ -9496,7 +9365,7 @@ __metadata: languageName: node linkType: hard -"rxjs@npm:^7.4.0, rxjs@npm:^7.5.5": +"rxjs@npm:^7.5.5": version: 7.8.1 resolution: "rxjs@npm:7.8.1" dependencies: @@ -9935,7 +9804,7 @@ __metadata: languageName: node linkType: hard -"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.0.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": +"string-width-cjs@npm:string-width@^4.2.0, string-width@npm:^4.1.0, string-width@npm:^4.2.0, string-width@npm:^4.2.3": version: 4.2.3 resolution: "string-width@npm:4.2.3" dependencies: @@ -10082,7 +9951,7 @@ __metadata: languageName: node linkType: hard -"supports-color@npm:^8, supports-color@npm:^8.0.0": +"supports-color@npm:^8.0.0": version: 8.1.1 resolution: "supports-color@npm:8.1.1" dependencies: @@ -10182,22 +10051,6 @@ __metadata: languageName: node linkType: hard -"threads@npm:^1.7.0": - version: 1.7.0 - resolution: "threads@npm:1.7.0" - dependencies: - callsites: "npm:^3.1.0" - debug: "npm:^4.2.0" - is-observable: "npm:^2.1.0" - observable-fns: "npm:^0.6.1" - tiny-worker: "npm:>= 2" - dependenciesMeta: - tiny-worker: - optional: true - checksum: 10c0/34114075c5eb253e937c01e0e51babb71eb1571c2fd4a8170645aa20c10186422324537d017d64bfd3de655cbb6647bf70d1d602260273b1e9750719d67f359a - languageName: node - linkType: hard - "through@npm:^2.3.6, through@npm:^2.3.8": version: 2.3.8 resolution: "through@npm:2.3.8" @@ -10205,15 +10058,6 @@ __metadata: languageName: node linkType: hard -"tiny-worker@npm:>= 2": - version: 2.3.0 - resolution: "tiny-worker@npm:2.3.0" - dependencies: - esm: "npm:^3.2.25" - checksum: 10c0/3106cace86e673216426a517e96fb72ce642ba79002554e4c6bceb585ba77cf5e5e68b452c752cada6136ae94fdbf11c56943a70de6c6bc6a2a3a9ae439746c9 - languageName: node - linkType: hard - "tinyglobby@npm:^0.2.9": version: 0.2.9 resolution: "tinyglobby@npm:0.2.9" @@ -10372,32 +10216,6 @@ __metadata: languageName: node linkType: hard -"ts-to-zod@npm:3.13.0": - version: 3.13.0 - resolution: "ts-to-zod@npm:3.13.0" - dependencies: - "@oclif/core": "npm:>=3.26.0" - "@typescript/vfs": "npm:^1.5.0" - case: "npm:^1.6.3" - chokidar: "npm:^3.5.1" - fs-extra: "npm:^11.1.1" - inquirer: "npm:^8.2.0" - lodash: "npm:^4.17.21" - ora: "npm:^5.4.0" - prettier: "npm:3.0.3" - rxjs: "npm:^7.4.0" - slash: "npm:^3.0.0" - threads: "npm:^1.7.0" - tslib: "npm:^2.3.1" - tsutils: "npm:^3.21.0" - typescript: "npm:^5.2.2" - zod: "npm:^3.23.8" - bin: - ts-to-zod: bin/run - checksum: 10c0/f385a8f2fd5f41bd8e4c8d68d8d8b949410b99181409427d22cec5eed763205bae46592da940c256fbd33877f1bebc9355280404e5e76e8e9cd3ff39f50a7803 - languageName: node - linkType: hard - "ts-toolbelt@npm:^9.6.0": version: 9.6.0 resolution: "ts-toolbelt@npm:9.6.0" @@ -10424,14 +10242,7 @@ __metadata: languageName: node linkType: hard -"tslib@npm:^1.8.1": - version: 1.14.1 - resolution: "tslib@npm:1.14.1" - checksum: 10c0/69ae09c49eea644bc5ebe1bca4fa4cc2c82b7b3e02f43b84bd891504edf66dbc6b2ec0eef31a957042de2269139e4acff911e6d186a258fb14069cd7f6febce2 - languageName: node - linkType: hard - -"tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.3.1, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3": +"tslib@npm:^2.0.3, tslib@npm:^2.1.0, tslib@npm:^2.4.0, tslib@npm:^2.5.0, tslib@npm:^2.6.2, tslib@npm:^2.6.3": version: 2.7.0 resolution: "tslib@npm:2.7.0" checksum: 10c0/469e1d5bf1af585742128827000711efa61010b699cb040ab1800bcd3ccdd37f63ec30642c9e07c4439c1db6e46345582614275daca3e0f4abae29b0083f04a6 @@ -10452,17 +10263,6 @@ __metadata: languageName: node linkType: hard -"tsutils@npm:^3.21.0": - version: 3.21.0 - resolution: "tsutils@npm:3.21.0" - dependencies: - tslib: "npm:^1.8.1" - peerDependencies: - typescript: ">=2.8.0 || >= 3.2.0-dev || >= 3.3.0-dev || >= 3.4.0-dev || >= 3.5.0-dev || >= 3.6.0-dev || >= 3.6.0-beta || >= 3.7.0-dev || >= 3.7.0-beta" - checksum: 10c0/02f19e458ec78ead8fffbf711f834ad8ecd2cc6ade4ec0320790713dccc0a412b99e7fd907c4cda2a1dc602c75db6f12e0108e87a5afad4b2f9e90a24cabd5a2 - languageName: node - linkType: hard - "tsx@npm:4.19.1": version: 4.19.1 resolution: "tsx@npm:4.19.1" @@ -10561,7 +10361,7 @@ __metadata: languageName: node linkType: hard -"typescript@npm:5.6.2, typescript@npm:^5.2.2": +"typescript@npm:5.6.2": version: 5.6.2 resolution: "typescript@npm:5.6.2" bin: @@ -10571,7 +10371,7 @@ __metadata: languageName: node linkType: hard -"typescript@patch:typescript@npm%3A5.6.2#optional!builtin, typescript@patch:typescript@npm%3A^5.2.2#optional!builtin": +"typescript@patch:typescript@npm%3A5.6.2#optional!builtin": version: 5.6.2 resolution: "typescript@patch:typescript@npm%3A5.6.2#optional!builtin::version=5.6.2&hash=8c6c40" bin: @@ -11007,15 +10807,6 @@ __metadata: languageName: node linkType: hard -"widest-line@npm:^3.1.0": - version: 3.1.0 - resolution: "widest-line@npm:3.1.0" - dependencies: - string-width: "npm:^4.0.0" - checksum: 10c0/b1e623adcfb9df35350dd7fc61295d6d4a1eaa65a406ba39c4b8360045b614af95ad10e05abf704936ed022569be438c4bfa02d6d031863c4166a238c301119f - languageName: node - linkType: hard - "wildcard@npm:^2.0.0": version: 2.0.1 resolution: "wildcard@npm:2.0.1" @@ -11030,13 +10821,6 @@ __metadata: languageName: node linkType: hard -"wordwrap@npm:^1.0.0": - version: 1.0.0 - resolution: "wordwrap@npm:1.0.0" - checksum: 10c0/7ed2e44f3c33c5c3e3771134d2b0aee4314c9e49c749e37f464bf69f2bcdf0cbf9419ca638098e2717cff4875c47f56a007532f6111c3319f557a2ca91278e92 - languageName: node - linkType: hard - "wrap-ansi-cjs@npm:wrap-ansi@^7.0.0, wrap-ansi@npm:^7.0.0": version: 7.0.0 resolution: "wrap-ansi@npm:7.0.0"