Skip to content
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

chore(typegen): move prettier formatting into generateAction #7333

Merged
merged 3 commits into from
Aug 7, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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()
})
})
Loading