From 3c34f0808afa1e6604e5144aecc66110329942ca Mon Sep 17 00:00:00 2001 From: Sindre Gulseth Date: Wed, 7 Aug 2024 13:19:17 +0200 Subject: [PATCH] chore(typegen): move prettier formatting into generateAction (#7333) * chore(typegen): move prettier formatting into generateAction This way we format the entire code in one scope * feat(typegen): generate one typemap instead of one per file * test(typegen): add integration test for generating typemap --- .../cli/src/actions/typegen/generateAction.ts | 55 +++++++++++++------ .../cli/src/workers/typegenGenerate.ts | 50 ++++++----------- ...working-typegen-overloadClientMethods.json | 5 ++ .../test/__snapshots__/typegen.test.ts.snap | 49 +++++++++++++++++ packages/@sanity/cli/test/typegen.test.ts | 20 +++++++ 5 files changed, 129 insertions(+), 50 deletions(-) create mode 100644 packages/@sanity/cli/test/__fixtures__/v3/working-typegen-overloadClientMethods.json diff --git a/packages/@sanity/cli/src/actions/typegen/generateAction.ts b/packages/@sanity/cli/src/actions/typegen/generateAction.ts index e46358fd03a..56fb145f8c2 100644 --- a/packages/@sanity/cli/src/actions/typegen/generateAction.ts +++ b/packages/@sanity/cli/src/actions/typegen/generateAction.ts @@ -3,7 +3,7 @@ import {dirname, join} from 'node:path' import {Worker} from 'node:worker_threads' import {readConfig} from '@sanity/codegen' -import prettier from 'prettier' +import {format as prettierFormat, resolveConfig as resolvePrettierConfig} from 'prettier' import {type CliCommandArguments, type CliCommandContext} from '../../types' import {getCliWorkerPath} from '../../util/cliWorker' @@ -61,13 +61,6 @@ export default async function typegenGenerateAction( const outputPath = join(process.cwd(), codegenConfig.generates) const outputDir = dirname(outputPath) await mkdir(outputDir, {recursive: true}) - - const prettierConfig = codegenConfig.formatGeneratedCode - ? await prettier.resolveConfig(outputPath).catch((err) => { - output.warn(`Failed to load prettier config: ${err.message}`) - return null - }) - : null const workerPath = await getCliWorkerPath('typegenGenerate') const spinner = output.spinner({}).start('Generating types') @@ -78,7 +71,6 @@ export default async function typegenGenerateAction( schemaPath: codegenConfig.schema, searchPath: codegenConfig.path, overloadClientMethods: codegenConfig.overloadClientMethods, - prettierConfig, } satisfies TypegenGenerateTypesWorkerData, // eslint-disable-next-line no-process-env env: process.env, @@ -123,6 +115,14 @@ export default async function typegenGenerateAction( return } + if (msg.type === 'typemap') { + let typeMapStr = `// Query TypeMap\n` + typeMapStr += msg.typeMap + typeFile.write(typeMapStr) + stats.size += Buffer.byteLength(typeMapStr) + return + } + let fileTypeString = `// Source: ${msg.filename}\n` if (msg.type === 'schema') { @@ -143,26 +143,47 @@ export default async function typegenGenerateAction( emptyUnionTypeNodesGenerated, } of msg.types) { fileTypeString += `// Variable: ${queryName}\n` - fileTypeString += `// Query: ${query.replace(/(\r\n|\n|\r)/gm, '')}\n` + fileTypeString += `// Query: ${query.replace(/(\r\n|\n|\r)/gm, '').trim()}\n` fileTypeString += type stats.queriesCount++ stats.typeNodesGenerated += typeNodesGenerated stats.unknownTypeNodesGenerated += unknownTypeNodesGenerated stats.emptyUnionTypeNodesGenerated += emptyUnionTypeNodesGenerated } - typeFile.write(fileTypeString) + typeFile.write(`${fileTypeString}\n`) stats.size += Buffer.byteLength(fileTypeString) } - - if (msg.type === 'typemap') { - typeFile.write(msg.typeMap) - stats.size += Buffer.byteLength(msg.typeMap) - } }) worker.addListener('error', reject) }) - typeFile.close() + await typeFile.close() + + const prettierConfig = codegenConfig.formatGeneratedCode + ? await resolvePrettierConfig(outputPath).catch((err) => { + output.warn(`Failed to load prettier config: ${err.message}`) + return null + }) + : null + + if (prettierConfig) { + const formatFile = await open(outputPath, constants.O_RDWR) + try { + const code = await formatFile.readFile() + const formattedCode = await prettierFormat(code.toString(), { + ...prettierConfig, + parser: 'typescript' as const, + }) + await formatFile.truncate() + await formatFile.write(formattedCode, 0) + + spinner.info('Formatted generated types with Prettier') + } catch (err) { + output.warn(`Failed to format generated types with Prettier: ${err.message}`) + } finally { + await formatFile.close() + } + } trace.log({ outputSize: stats.size, diff --git a/packages/@sanity/cli/src/workers/typegenGenerate.ts b/packages/@sanity/cli/src/workers/typegenGenerate.ts index 5c0cad9839d..2d70fc0ae7f 100644 --- a/packages/@sanity/cli/src/workers/typegenGenerate.ts +++ b/packages/@sanity/cli/src/workers/typegenGenerate.ts @@ -10,7 +10,6 @@ import { } from '@sanity/codegen' import createDebug from 'debug' import {typeEvaluate, type TypeNode} from 'groq-js' -import {format as prettierFormat, type Options as PrettierOptions} from 'prettier' const $info = createDebug('sanity:codegen:generate:info') const $warn = createDebug('sanity:codegen:generate:warn') @@ -20,7 +19,6 @@ export interface TypegenGenerateTypesWorkerData { workspaceName?: string schemaPath: string searchPath: string | string[] - prettierConfig: PrettierOptions | null overloadClientMethods?: boolean } @@ -52,7 +50,6 @@ export type TypegenGenerateTypesWorkerMessage = } | { type: 'typemap' - filename: string typeMap: string } | { @@ -67,35 +64,18 @@ const opts = _workerData as TypegenGenerateTypesWorkerData registerBabel() -function maybeFormatCode(code: string, prettierConfig: PrettierOptions | null): Promise { - if (!prettierConfig) { - return Promise.resolve(`${code}\n`) // add an extra new newline, poor mans formatting - } - - try { - return prettierFormat(code, { - ...prettierConfig, - parser: 'typescript' as const, - }) - } catch (err) { - $warn(`Error formatting: ${err.message}`) - } - return Promise.resolve(code) -} - async function main() { const schema = await readSchema(opts.schemaPath) const typeGenerator = new TypeGenerator(schema) - const schemaTypes = await maybeFormatCode( - [typeGenerator.generateSchemaTypes(), TypeGenerator.generateKnownTypes()].join('\n').trim(), - opts.prettierConfig, - ) + const schemaTypes = [typeGenerator.generateSchemaTypes(), TypeGenerator.generateKnownTypes()] + .join('\n') + .trim() const resolver = getResolver() parentPort?.postMessage({ type: 'schema', - schema: schemaTypes, + schema: `${schemaTypes.trim()}\n`, filename: 'schema.json', length: schema.length, } satisfies TypegenGenerateTypesWorkerMessage) @@ -105,6 +85,8 @@ async function main() { resolver, }) + const allQueries = [] + for await (const result of queries) { if (result.type === 'error') { parentPort?.postMessage({ @@ -134,7 +116,6 @@ async function main() { const typeName = `${queryName}Result` const type = typeGenerator.generateTypeNodeTypes(typeName, queryTypes) - const code = await maybeFormatCode(type.trim(), opts.prettierConfig) const queryTypeStats = walkAndCountQueryTypeNodeStats(queryTypes) fileQueryTypes.push({ @@ -142,7 +123,7 @@ async function main() { query, typeName, typeNode: queryTypes, - type: code, + type: `${type.trim()}\n`, unknownTypeNodesGenerated: queryTypeStats.unknownTypes, typeNodesGenerated: queryTypeStats.allTypes, emptyUnionTypeNodesGenerated: queryTypeStats.emptyUnions, @@ -169,16 +150,19 @@ async function main() { } satisfies TypegenGenerateTypesWorkerMessage) } - if (fileQueryTypes.length > 0 && opts.overloadClientMethods) { - const typeMap = typeGenerator.generateQueryMap(fileQueryTypes) - parentPort?.postMessage({ - type: 'typemap', - filename: result.filename, - typeMap, - } satisfies TypegenGenerateTypesWorkerMessage) + if (fileQueryTypes.length > 0) { + allQueries.push(...fileQueryTypes) } } + if (opts.overloadClientMethods && allQueries.length > 0) { + const typeMap = `${typeGenerator.generateQueryMap(allQueries).trim()}\n` + parentPort?.postMessage({ + type: 'typemap', + typeMap, + } satisfies TypegenGenerateTypesWorkerMessage) + } + parentPort?.postMessage({ type: 'complete', } satisfies TypegenGenerateTypesWorkerMessage) diff --git a/packages/@sanity/cli/test/__fixtures__/v3/working-typegen-overloadClientMethods.json b/packages/@sanity/cli/test/__fixtures__/v3/working-typegen-overloadClientMethods.json new file mode 100644 index 00000000000..0ad1ab7f47f --- /dev/null +++ b/packages/@sanity/cli/test/__fixtures__/v3/working-typegen-overloadClientMethods.json @@ -0,0 +1,5 @@ +{ + "schema": "./working-schema.json", + "generates": "./out/types.ts", + "overloadClientMethods": true +} diff --git a/packages/@sanity/cli/test/__snapshots__/typegen.test.ts.snap b/packages/@sanity/cli/test/__snapshots__/typegen.test.ts.snap index daece12c38d..a8ea1d74606 100644 --- a/packages/@sanity/cli/test/__snapshots__/typegen.test.ts.snap +++ b/packages/@sanity/cli/test/__snapshots__/typegen.test.ts.snap @@ -40,3 +40,52 @@ export declare const internalGroqTypeReferenceTo: unique symbol; export type PAGE_QUERYResult = null; " `; + +exports[`CLI: \`sanity typegen\` sanity typegen generate: with overloadClientMethods 1`] = ` +"/** + * --------------------------------------------------------------------------------- + * This file has been generated by Sanity TypeGen. + * Command: \`sanity typegen generate\` + * + * Any modifications made directly to this file will be overwritten the next time + * the TypeScript definitions are generated. Please make changes to the Sanity + * schema definitions and/or GROQ queries if you need to update these types. + * + * For more information on how to use Sanity TypeGen, visit the official documentation: + * https://www.sanity.io/docs/sanity-typegen + * --------------------------------------------------------------------------------- + */ + +// Source: schema.json +export type Person = { + _id: string; + _type: 'person'; + _createdAt: string; + _updatedAt: string; + _rev: string; + name?: string; + slug?: Slug; +}; + +export type Slug = { + _type: 'slug'; + current?: string; + source?: string; +}; + +export type AllSanitySchemaTypes = Person | Slug; +export declare const internalGroqTypeReferenceTo: unique symbol; +// Source: ./src/queries.ts +// Variable: PAGE_QUERY +// Query: *[_type == \\"page\\" && slug.current == $slug][0] +export type PAGE_QUERYResult = null; + +// Query TypeMap +import '@sanity/client'; +declare module '@sanity/client' { + interface SanityQueries { + '*[_type == \\"page\\" && slug.current == $slug][0]': PAGE_QUERYResult; + } +} +" +`; diff --git a/packages/@sanity/cli/test/typegen.test.ts b/packages/@sanity/cli/test/typegen.test.ts index fc9a0e252a6..96c0b7a7890 100644 --- a/packages/@sanity/cli/test/typegen.test.ts +++ b/packages/@sanity/cli/test/typegen.test.ts @@ -69,4 +69,24 @@ describeCliTest('CLI: `sanity typegen`', () => { expect(types.toString()).toContain(`'person'`) expect(types.toString()).toMatchSnapshot() }) + + test('sanity typegen generate: with overloadClientMethods', async () => { + // Write a prettier config to the output folder, with single quotes. The defeault is double quotes. + await writeFile(`${studiosPath}/v3/out/.prettierrc`, '{\n "singleQuote": true\n}\n') + const result = await runSanityCmdCommand('v3', [ + 'typegen', + 'generate', + '--config-path', + 'working-typegen-overloadClientMethods.json', + ]) + + expect(result.code).toBe(0) + expect(result.stderr).toContain( + 'Generated TypeScript types for 2 schema types and 1 GROQ queries in 1 file', + ) + + const types = await readFile(`${studiosPath}/v3/out/types.ts`) + expect(types.toString()).toContain(`'person'`) + expect(types.toString()).toMatchSnapshot() + }) })