Skip to content

Commit

Permalink
chore(typegen): move prettier formatting into generateAction (#7333)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
sgulseth authored Aug 7, 2024
1 parent d60dc6d commit 3c34f08
Show file tree
Hide file tree
Showing 5 changed files with 129 additions and 50 deletions.
55 changes: 38 additions & 17 deletions packages/@sanity/cli/src/actions/typegen/generateAction.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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'
Expand Down Expand Up @@ -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')
Expand All @@ -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,
Expand Down Expand Up @@ -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') {
Expand All @@ -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,
Expand Down
50 changes: 17 additions & 33 deletions packages/@sanity/cli/src/workers/typegenGenerate.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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')
Expand All @@ -20,7 +19,6 @@ export interface TypegenGenerateTypesWorkerData {
workspaceName?: string
schemaPath: string
searchPath: string | string[]
prettierConfig: PrettierOptions | null
overloadClientMethods?: boolean
}

Expand Down Expand Up @@ -52,7 +50,6 @@ export type TypegenGenerateTypesWorkerMessage =
}
| {
type: 'typemap'
filename: string
typeMap: string
}
| {
Expand All @@ -67,35 +64,18 @@ const opts = _workerData as TypegenGenerateTypesWorkerData

registerBabel()

function maybeFormatCode(code: string, prettierConfig: PrettierOptions | null): Promise<string> {
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)
Expand All @@ -105,6 +85,8 @@ async function main() {
resolver,
})

const allQueries = []

for await (const result of queries) {
if (result.type === 'error') {
parentPort?.postMessage({
Expand Down Expand Up @@ -134,15 +116,14 @@ 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({
queryName,
query,
typeName,
typeNode: queryTypes,
type: code,
type: `${type.trim()}\n`,
unknownTypeNodesGenerated: queryTypeStats.unknownTypes,
typeNodesGenerated: queryTypeStats.allTypes,
emptyUnionTypeNodesGenerated: queryTypeStats.emptyUnions,
Expand All @@ -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)
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
{
"schema": "./working-schema.json",
"generates": "./out/types.ts",
"overloadClientMethods": true
}
49 changes: 49 additions & 0 deletions packages/@sanity/cli/test/__snapshots__/typegen.test.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -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;
}
}
"
`;
20 changes: 20 additions & 0 deletions packages/@sanity/cli/test/typegen.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -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()
})
})

0 comments on commit 3c34f08

Please sign in to comment.