@sanity/cli 3.88.1-typegen-experimental.0 → 3.88.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/_chunks-cjs/generateAction.js +111 -113
- package/lib/_chunks-cjs/generateAction.js.map +1 -1
- package/lib/_chunks-cjs/loadEnv.js +3 -3
- package/lib/_chunks-cjs/loadEnv.js.map +1 -1
- package/lib/workers/typegenGenerate.d.ts +33 -144
- package/lib/workers/typegenGenerate.js +112 -83
- package/lib/workers/typegenGenerate.js.map +1 -1
- package/package.json +19 -17
- package/src/actions/typegen/generate.telemetry.ts +3 -9
- package/src/actions/typegen/generateAction.ts +152 -159
- package/src/cli.ts +0 -0
- package/src/commands/projects/listProjectsCommand.ts +0 -0
- package/src/commands/projects/projectsGroup.ts +0 -0
- package/src/workers/typegenGenerate.ts +183 -181
- package/lib/_chunks-cjs/workerChannel.js +0 -84
- package/lib/_chunks-cjs/workerChannel.js.map +0 -1
- package/src/util/__tests__/workerChannel.test.ts +0 -222
- package/src/util/workerChannel.ts +0 -312
@@ -1,17 +1,15 @@
|
|
1
|
-
/* eslint-disable max-statements */
|
2
1
|
import {constants, mkdir, open, stat} from 'node:fs/promises'
|
3
2
|
import {dirname, join} from 'node:path'
|
4
|
-
import process from 'node:process'
|
5
3
|
import {Worker} from 'node:worker_threads'
|
6
4
|
|
7
|
-
import {
|
5
|
+
import {readConfig} from '@sanity/codegen'
|
6
|
+
import {format as prettierFormat, resolveConfig as resolvePrettierConfig} from 'prettier'
|
8
7
|
|
9
8
|
import {type CliCommandArguments, type CliCommandContext} from '../../types'
|
10
9
|
import {getCliWorkerPath} from '../../util/cliWorker'
|
11
|
-
import {createReceiver} from '../../util/workerChannel'
|
12
10
|
import {
|
13
11
|
type TypegenGenerateTypesWorkerData,
|
14
|
-
type
|
12
|
+
type TypegenGenerateTypesWorkerMessage,
|
15
13
|
} from '../../workers/typegenGenerate'
|
16
14
|
import {TypesGeneratedTrace} from './generate.telemetry'
|
17
15
|
|
@@ -19,8 +17,7 @@ export interface TypegenGenerateTypesCommandFlags {
|
|
19
17
|
'config-path'?: string
|
20
18
|
}
|
21
19
|
|
22
|
-
const
|
23
|
-
const GENERATED_FILE_WARNING = `/**
|
20
|
+
const generatedFileWarning = `/**
|
24
21
|
* ---------------------------------------------------------------------------------
|
25
22
|
* This file has been generated by Sanity TypeGen.
|
26
23
|
* Command: \`sanity typegen generate\`
|
@@ -34,20 +31,6 @@ const GENERATED_FILE_WARNING = `/**
|
|
34
31
|
* ---------------------------------------------------------------------------------
|
35
32
|
*/\n\n`
|
36
33
|
|
37
|
-
const percentageFormatter = new Intl.NumberFormat('en-US', {
|
38
|
-
style: 'percent',
|
39
|
-
minimumFractionDigits: 1,
|
40
|
-
maximumFractionDigits: 1,
|
41
|
-
})
|
42
|
-
|
43
|
-
const percent = (value: number): string => percentageFormatter.format(Math.min(value, 1))
|
44
|
-
|
45
|
-
const count = (
|
46
|
-
amount: number,
|
47
|
-
plural: string,
|
48
|
-
singular: string = plural.slice(0, Math.max(0, plural.length - 1)),
|
49
|
-
): string => `${amount.toLocaleString('en-US')} ${amount === 1 ? singular : plural}`
|
50
|
-
|
51
34
|
export default async function typegenGenerateAction(
|
52
35
|
args: CliCommandArguments<TypegenGenerateTypesCommandFlags>,
|
53
36
|
context: CliCommandContext,
|
@@ -58,160 +41,170 @@ export default async function typegenGenerateAction(
|
|
58
41
|
const trace = telemetry.trace(TypesGeneratedTrace)
|
59
42
|
trace.start()
|
60
43
|
|
61
|
-
const
|
62
|
-
spinner.start()
|
44
|
+
const codegenConfig = await readConfig(flags['config-path'] || 'sanity-typegen.json')
|
63
45
|
|
64
|
-
let codegenConfig
|
65
|
-
const configPath = flags['config-path'] ?? DEFAULT_CONFIG_PATH
|
66
46
|
try {
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
if (error?.code !== 'ENOENT') throw error
|
71
|
-
codegenConfig = DEFAULT_CONFIG
|
72
|
-
|
73
|
-
if (configPath !== DEFAULT_CONFIG_PATH) {
|
74
|
-
spinner.warn(
|
75
|
-
`Configuration file not found at specified path "${configPath}". Falling back to default settings.`,
|
76
|
-
)
|
47
|
+
const schemaStats = await stat(codegenConfig.schema)
|
48
|
+
if (!schemaStats.isFile()) {
|
49
|
+
throw new Error(`Schema path is not a file: ${codegenConfig.schema}`)
|
77
50
|
}
|
51
|
+
} catch (err) {
|
52
|
+
if (err.code === 'ENOENT') {
|
53
|
+
// If the user has not provided a specific schema path (eg we're using the default), give some help
|
54
|
+
const hint =
|
55
|
+
codegenConfig.schema === './schema.json' ? ` - did you run "sanity schema extract"?` : ''
|
56
|
+
throw new Error(`Schema file not found: ${codegenConfig.schema}${hint}`)
|
57
|
+
}
|
58
|
+
throw err
|
78
59
|
}
|
79
|
-
|
80
|
-
|
81
|
-
|
82
|
-
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
|
92
|
-
|
93
|
-
|
94
|
-
|
95
|
-
schemas,
|
96
|
-
searchPath,
|
97
|
-
overloadClientMethods,
|
98
|
-
augmentGroqModule,
|
99
|
-
}
|
100
|
-
const worker = new Worker(await getCliWorkerPath('typegenGenerate'), {
|
101
|
-
workerData,
|
60
|
+
|
61
|
+
const outputPath = join(process.cwd(), codegenConfig.generates)
|
62
|
+
const outputDir = dirname(outputPath)
|
63
|
+
await mkdir(outputDir, {recursive: true})
|
64
|
+
const workerPath = await getCliWorkerPath('typegenGenerate')
|
65
|
+
|
66
|
+
const spinner = output.spinner({}).start('Generating types')
|
67
|
+
|
68
|
+
const worker = new Worker(workerPath, {
|
69
|
+
workerData: {
|
70
|
+
workDir,
|
71
|
+
schemaPath: codegenConfig.schema,
|
72
|
+
searchPath: codegenConfig.path,
|
73
|
+
overloadClientMethods: codegenConfig.overloadClientMethods,
|
74
|
+
} satisfies TypegenGenerateTypesWorkerData,
|
75
|
+
// eslint-disable-next-line no-process-env
|
102
76
|
env: process.env,
|
103
77
|
})
|
104
|
-
const receiver = createReceiver<TypegenWorkerChannel>(worker)
|
105
78
|
|
106
|
-
|
107
|
-
|
108
|
-
|
79
|
+
const typeFile = await open(
|
80
|
+
outputPath,
|
81
|
+
// eslint-disable-next-line no-bitwise
|
82
|
+
constants.O_TRUNC | constants.O_CREAT | constants.O_WRONLY,
|
83
|
+
)
|
84
|
+
|
85
|
+
typeFile.write(generatedFileWarning)
|
86
|
+
|
87
|
+
const stats = {
|
88
|
+
queryFilesCount: 0,
|
89
|
+
errors: 0,
|
90
|
+
queriesCount: 0,
|
91
|
+
schemaTypesCount: 0,
|
92
|
+
unknownTypeNodesGenerated: 0,
|
93
|
+
typeNodesGenerated: 0,
|
94
|
+
emptyUnionTypeNodesGenerated: 0,
|
95
|
+
size: 0,
|
96
|
+
}
|
109
97
|
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
123
|
-
|
124
|
-
|
125
|
-
const schemaTypesCount = count(schemaStats.schemaTypesCount, 'schema types')
|
126
|
-
const schemaCount = count(schemaStats.schemaCount, 'schemas')
|
127
|
-
spinner.succeed(
|
128
|
-
`Generated ${schemaTypesCount}${schemas.length > 1 ? ` from ${schemaCount}` : ''}`,
|
129
|
-
)
|
130
|
-
|
131
|
-
spinner.start('Generating query types…')
|
132
|
-
const expectedFiles = (await receiver.event.fileCount()).fileCount
|
133
|
-
const expectedFileCount = count(expectedFiles, 'files')
|
134
|
-
|
135
|
-
for await (const {
|
136
|
-
progress,
|
137
|
-
...queryResult
|
138
|
-
} of receiver.stream.generatedQueryResultDeclaration()) {
|
139
|
-
const queryCount = count(progress.queriesCount, 'queries', 'query')
|
140
|
-
const projectionCount = count(progress.projectionsCount, 'projections')
|
141
|
-
spinner.text =
|
142
|
-
`Generating query types… (${percent(progress.filesCount / expectedFiles)})\n` +
|
143
|
-
` └─ Processed ${progress.filesCount} of ${expectedFileCount}. Found ${queryCount}, ${projectionCount}.`
|
144
|
-
|
145
|
-
if (queryResult.type === 'error') {
|
146
|
-
spinner.fail(queryResult.message)
|
98
|
+
await new Promise<void>((resolve, reject) => {
|
99
|
+
worker.addListener('message', (msg: TypegenGenerateTypesWorkerMessage) => {
|
100
|
+
if (msg.type === 'error') {
|
101
|
+
if (msg.fatal) {
|
102
|
+
trace.error(msg.error)
|
103
|
+
reject(msg.error)
|
104
|
+
return
|
105
|
+
}
|
106
|
+
const errorMessage = msg.filename
|
107
|
+
? `${msg.error.message} in "${msg.filename}"`
|
108
|
+
: msg.error.message
|
109
|
+
spinner.fail(errorMessage)
|
110
|
+
stats.errors++
|
111
|
+
return
|
147
112
|
}
|
148
|
-
|
149
|
-
|
150
|
-
|
113
|
+
if (msg.type === 'complete') {
|
114
|
+
resolve()
|
115
|
+
return
|
151
116
|
}
|
152
|
-
}
|
153
117
|
|
154
|
-
|
155
|
-
|
156
|
-
|
157
|
-
|
158
|
-
|
159
|
-
|
160
|
-
const queryTypesCount = count(queryStats.queriesCount, 'query types')
|
161
|
-
const projectionTypesCount = count(queryStats.projectionsCount, 'projection types')
|
162
|
-
const scannedFilesCount = count(queryStats.totalScannedFilesCount, 'scanned files')
|
163
|
-
spinner.succeed(
|
164
|
-
`Generated ${queryTypesCount} and ${projectionTypesCount} from ${scannedFilesCount}`,
|
165
|
-
)
|
166
|
-
|
167
|
-
if (formatGeneratedCode) {
|
168
|
-
spinner.start(`Formatting generated types with prettier…`)
|
169
|
-
|
170
|
-
try {
|
171
|
-
const prettier = await import('prettier')
|
172
|
-
const prettierConfig = await prettier.resolveConfig(outputPath)
|
173
|
-
|
174
|
-
fileHandle = await open(outputPath, constants.O_RDWR)
|
175
|
-
const code = await fileHandle.readFile({encoding: 'utf-8'})
|
176
|
-
const formattedCode = await prettier.format(code, {
|
177
|
-
...prettierConfig,
|
178
|
-
parser: 'typescript' as const,
|
179
|
-
})
|
180
|
-
await fileHandle.truncate()
|
181
|
-
await fileHandle.write(formattedCode, 0)
|
182
|
-
await fileHandle.close()
|
183
|
-
fileHandle = null
|
184
|
-
|
185
|
-
spinner.succeed('Formatted generated types with prettier')
|
186
|
-
} catch (err) {
|
187
|
-
spinner.warn(`Failed to format generated types with prettier: ${err.message}`)
|
118
|
+
if (msg.type === 'typemap') {
|
119
|
+
let typeMapStr = `// Query TypeMap\n`
|
120
|
+
typeMapStr += msg.typeMap
|
121
|
+
typeFile.write(typeMapStr)
|
122
|
+
stats.size += Buffer.byteLength(typeMapStr)
|
123
|
+
return
|
188
124
|
}
|
189
|
-
}
|
190
125
|
|
191
|
-
|
192
|
-
|
126
|
+
let fileTypeString = `// Source: ${msg.filename}\n`
|
127
|
+
|
128
|
+
if (msg.type === 'schema') {
|
129
|
+
stats.schemaTypesCount += msg.length
|
130
|
+
fileTypeString += msg.schema
|
131
|
+
typeFile.write(fileTypeString)
|
132
|
+
return
|
133
|
+
}
|
193
134
|
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
135
|
+
if (msg.type === 'types') {
|
136
|
+
stats.queryFilesCount++
|
137
|
+
for (const {
|
138
|
+
queryName,
|
139
|
+
query,
|
140
|
+
type,
|
141
|
+
typeNodesGenerated,
|
142
|
+
unknownTypeNodesGenerated,
|
143
|
+
emptyUnionTypeNodesGenerated,
|
144
|
+
} of msg.types) {
|
145
|
+
fileTypeString += `// Variable: ${queryName}\n`
|
146
|
+
fileTypeString += `// Query: ${query.replace(/(\r\n|\n|\r)/gm, '').trim()}\n`
|
147
|
+
fileTypeString += type
|
148
|
+
stats.queriesCount++
|
149
|
+
stats.typeNodesGenerated += typeNodesGenerated
|
150
|
+
stats.unknownTypeNodesGenerated += unknownTypeNodesGenerated
|
151
|
+
stats.emptyUnionTypeNodesGenerated += emptyUnionTypeNodesGenerated
|
152
|
+
}
|
153
|
+
typeFile.write(`${fileTypeString}\n`)
|
154
|
+
stats.size += Buffer.byteLength(fileTypeString)
|
155
|
+
}
|
200
156
|
})
|
157
|
+
worker.addListener('error', reject)
|
158
|
+
})
|
201
159
|
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
207
|
-
|
160
|
+
await typeFile.close()
|
161
|
+
|
162
|
+
const prettierConfig = codegenConfig.formatGeneratedCode
|
163
|
+
? await resolvePrettierConfig(outputPath).catch((err) => {
|
164
|
+
output.warn(`Failed to load prettier config: ${err.message}`)
|
165
|
+
return null
|
166
|
+
})
|
167
|
+
: null
|
168
|
+
|
169
|
+
if (prettierConfig) {
|
170
|
+
const formatFile = await open(outputPath, constants.O_RDWR)
|
171
|
+
try {
|
172
|
+
const code = await formatFile.readFile()
|
173
|
+
const formattedCode = await prettierFormat(code.toString(), {
|
174
|
+
...prettierConfig,
|
175
|
+
parser: 'typescript' as const,
|
176
|
+
})
|
177
|
+
await formatFile.truncate()
|
178
|
+
await formatFile.write(formattedCode, 0)
|
179
|
+
|
180
|
+
spinner.info('Formatted generated types with Prettier')
|
181
|
+
} catch (err) {
|
182
|
+
output.warn(`Failed to format generated types with Prettier: ${err.message}`)
|
183
|
+
} finally {
|
184
|
+
await formatFile.close()
|
208
185
|
}
|
209
|
-
} catch (err) {
|
210
|
-
trace.error(err)
|
211
|
-
throw err
|
212
|
-
} finally {
|
213
|
-
await fileHandle?.close()
|
214
|
-
await receiver.dispose()
|
215
|
-
trace.complete()
|
216
186
|
}
|
187
|
+
|
188
|
+
trace.log({
|
189
|
+
outputSize: stats.size,
|
190
|
+
queriesCount: stats.queriesCount,
|
191
|
+
schemaTypesCount: stats.schemaTypesCount,
|
192
|
+
queryFilesCount: stats.queryFilesCount,
|
193
|
+
filesWithErrors: stats.errors,
|
194
|
+
typeNodesGenerated: stats.typeNodesGenerated,
|
195
|
+
unknownTypeNodesGenerated: stats.unknownTypeNodesGenerated,
|
196
|
+
unknownTypeNodesRatio:
|
197
|
+
stats.typeNodesGenerated > 0 ? stats.unknownTypeNodesGenerated / stats.typeNodesGenerated : 0,
|
198
|
+
emptyUnionTypeNodesGenerated: stats.emptyUnionTypeNodesGenerated,
|
199
|
+
configOverloadClientMethods: codegenConfig.overloadClientMethods,
|
200
|
+
})
|
201
|
+
|
202
|
+
trace.complete()
|
203
|
+
if (stats.errors > 0) {
|
204
|
+
spinner.warn(`Encountered errors in ${stats.errors} files while generating types`)
|
205
|
+
}
|
206
|
+
|
207
|
+
spinner.succeed(
|
208
|
+
`Generated TypeScript types for ${stats.schemaTypesCount} schema types and ${stats.queriesCount} GROQ queries in ${stats.queryFilesCount} files into: ${codegenConfig.generates}`,
|
209
|
+
)
|
217
210
|
}
|
package/src/cli.ts
CHANGED
File without changes
|
File without changes
|
File without changes
|