@sanity/cli 3.87.1 → 3.88.1-typegen-experimental.0

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.
Files changed (39) hide show
  1. package/lib/_chunks-cjs/cli.js +58780 -56791
  2. package/lib/_chunks-cjs/cli.js.map +1 -1
  3. package/lib/_chunks-cjs/generateAction.js +113 -111
  4. package/lib/_chunks-cjs/generateAction.js.map +1 -1
  5. package/lib/_chunks-cjs/loadEnv.js +3 -3
  6. package/lib/_chunks-cjs/loadEnv.js.map +1 -1
  7. package/lib/_chunks-cjs/workerChannel.js +84 -0
  8. package/lib/_chunks-cjs/workerChannel.js.map +1 -0
  9. package/lib/workers/typegenGenerate.d.ts +144 -33
  10. package/lib/workers/typegenGenerate.js +83 -112
  11. package/lib/workers/typegenGenerate.js.map +1 -1
  12. package/package.json +20 -22
  13. package/src/actions/init-project/templates/appQuickstart.ts +2 -2
  14. package/src/actions/init-project/templates/appSanityUi.ts +2 -2
  15. package/src/actions/typegen/generate.telemetry.ts +9 -3
  16. package/src/actions/typegen/generateAction.ts +159 -152
  17. package/src/cli.ts +0 -0
  18. package/src/commands/blueprints/addBlueprintsCommand.ts +52 -56
  19. package/src/commands/blueprints/blueprintsGroup.ts +0 -1
  20. package/src/commands/blueprints/configBlueprintsCommand.ts +50 -74
  21. package/src/commands/blueprints/deployBlueprintsCommand.ts +41 -133
  22. package/src/commands/blueprints/destroyBlueprintsCommand.ts +76 -0
  23. package/src/commands/blueprints/infoBlueprintsCommand.ts +29 -51
  24. package/src/commands/blueprints/initBlueprintsCommand.ts +55 -73
  25. package/src/commands/blueprints/logsBlueprintsCommand.ts +43 -81
  26. package/src/commands/blueprints/planBlueprintsCommand.ts +26 -36
  27. package/src/commands/blueprints/stacksBlueprintsCommand.ts +43 -51
  28. package/src/commands/functions/devFunctionsCommand.ts +1 -2
  29. package/src/commands/functions/envFunctionsCommand.ts +55 -46
  30. package/src/commands/functions/functionsGroup.ts +1 -2
  31. package/src/commands/functions/logsFunctionsCommand.ts +101 -58
  32. package/src/commands/functions/testFunctionsCommand.ts +56 -36
  33. package/src/commands/index.ts +6 -4
  34. package/src/commands/projects/listProjectsCommand.ts +0 -0
  35. package/src/commands/projects/projectsGroup.ts +0 -0
  36. package/src/util/__tests__/workerChannel.test.ts +222 -0
  37. package/src/util/workerChannel.ts +312 -0
  38. package/src/workers/typegenGenerate.ts +181 -183
  39. package/templates/app-sanity-ui/src/ExampleComponent.tsx +1 -1
@@ -1,229 +1,227 @@
1
+ /* eslint-disable max-statements */
2
+ import {stat} from 'node:fs/promises'
1
3
  import {isMainThread, parentPort, workerData as _workerData} from 'node:worker_threads'
2
4
 
3
5
  import {
6
+ DEFAULT_CONFIG,
4
7
  findQueriesInPath,
5
8
  getResolver,
6
9
  readSchema,
7
10
  registerBabel,
8
- safeParseQuery,
9
11
  TypeGenerator,
10
12
  } from '@sanity/codegen'
11
- import createDebug from 'debug'
12
- import {typeEvaluate, type TypeNode} from 'groq-js'
13
+ import {type SchemaType} from 'groq-js'
13
14
 
14
- const $info = createDebug('sanity:codegen:generate:info')
15
- const $warn = createDebug('sanity:codegen:generate:warn')
15
+ import {
16
+ createReporter,
17
+ type WorkerChannel,
18
+ type WorkerChannelEvent,
19
+ type WorkerChannelStream,
20
+ } from '../util/workerChannel'
21
+
22
+ const DEFAULT_SCHEMA_PATH = DEFAULT_CONFIG.schemas[0].schemaPath
16
23
 
17
24
  export interface TypegenGenerateTypesWorkerData {
18
25
  workDir: string
19
- workspaceName?: string
20
- schemaPath: string
26
+ schemas: {schemaPath: string; schemaId: string}[]
21
27
  searchPath: string | string[]
22
- overloadClientMethods?: boolean
28
+ overloadClientMethods: boolean
29
+ augmentGroqModule: boolean
23
30
  }
24
31
 
25
- export type TypegenGenerateTypesWorkerMessage =
26
- | {
27
- type: 'error'
28
- error: Error
29
- fatal: boolean
30
- query?: string
31
- filename?: string
32
- }
33
- | {
34
- type: 'types'
35
- filename: string
36
- types: {
37
- queryName: string
38
- query: string
39
- type: string
40
- unknownTypeNodesGenerated: number
41
- typeNodesGenerated: number
42
- emptyUnionTypeNodesGenerated: number
43
- }[]
44
- }
45
- | {
46
- type: 'schema'
47
- filename: string
48
- schema: string
49
- length: number
50
- }
51
- | {
52
- type: 'typemap'
53
- typeMap: string
32
+ interface QueryProgress {
33
+ queriesCount: number
34
+ projectionsCount: number
35
+ filesCount: number
36
+ }
37
+
38
+ /** @internal */
39
+ export type TypegenWorkerChannel = WorkerChannel<{
40
+ loadedSchemas: WorkerChannelEvent
41
+ generatedSchemaDeclarations: WorkerChannelEvent<{
42
+ code: string
43
+ schemaStats: {
44
+ schemaTypesCount: number
45
+ schemaCount: number
54
46
  }
55
- | {
56
- type: 'complete'
47
+ }>
48
+ fileCount: WorkerChannelEvent<{fileCount: number}>
49
+ generatedQueryResultDeclaration: WorkerChannelStream<
50
+ | {
51
+ type: 'progress'
52
+ progress: QueryProgress
53
+ }
54
+ | {
55
+ type: 'declaration'
56
+ code: string
57
+ progress: QueryProgress
58
+ }
59
+ | {
60
+ type: 'error'
61
+ message: string
62
+ progress: QueryProgress
63
+ }
64
+ >
65
+ generationComplete: WorkerChannelEvent<{
66
+ augmentedQueryResultDeclarations: {code: string}
67
+ queryStats: {
68
+ queriesCount: number
69
+ projectionsCount: number
70
+ totalScannedFilesCount: number
71
+ queryFilesCount: number
72
+ projectionFilesCount: number
73
+ filesWithErrors: number
74
+ errorCount: number
75
+ typeNodesGenerated: number
76
+ unknownTypeNodesGenerated: number
77
+ unknownTypeNodesRatio: number
78
+ emptyUnionTypeNodesGenerated: number
57
79
  }
80
+ }>
81
+ }>
58
82
 
59
83
  if (isMainThread || !parentPort) {
60
84
  throw new Error('This module must be run as a worker thread')
61
85
  }
62
86
 
87
+ const report = createReporter<TypegenWorkerChannel>(parentPort)
63
88
  const opts = _workerData as TypegenGenerateTypesWorkerData
64
89
 
65
- registerBabel()
66
-
67
90
  async function main() {
68
- const schema = await readSchema(opts.schemaPath)
69
-
70
- const typeGenerator = new TypeGenerator(schema)
71
- const schemaTypes = [typeGenerator.generateSchemaTypes(), TypeGenerator.generateKnownTypes()]
72
- .join('\n')
73
- .trim()
74
- const resolver = getResolver()
75
-
76
- parentPort?.postMessage({
77
- type: 'schema',
78
- schema: `${schemaTypes.trim()}\n`,
79
- filename: 'schema.json',
80
- length: schema.length,
81
- } satisfies TypegenGenerateTypesWorkerMessage)
82
-
83
- const queries = findQueriesInPath({
84
- path: opts.searchPath,
85
- resolver,
86
- })
91
+ const schemas: {schema: SchemaType; schemaId: string; filename: string}[] = []
92
+
93
+ for (const {schemaId, schemaPath} of opts.schemas) {
94
+ try {
95
+ const schemaStats = await stat(schemaPath)
96
+ if (!schemaStats.isFile()) {
97
+ throw new Error(
98
+ `Failed to load schema "${schemaId}". Schema path is not a file: ${schemaPath}`,
99
+ )
100
+ }
87
101
 
88
- const allQueries = []
89
-
90
- for await (const result of queries) {
91
- if (result.type === 'error') {
92
- parentPort?.postMessage({
93
- type: 'error',
94
- error: result.error,
95
- fatal: false,
96
- filename: result.filename,
97
- } satisfies TypegenGenerateTypesWorkerMessage)
98
- continue
99
- }
100
- $info(`Processing ${result.queries.length} queries in "${result.filename}"...`)
101
-
102
- const fileQueryTypes: {
103
- queryName: string
104
- query: string
105
- type: string
106
- typeName: string
107
- typeNode: TypeNode
108
- unknownTypeNodesGenerated: number
109
- typeNodesGenerated: number
110
- emptyUnionTypeNodesGenerated: number
111
- }[] = []
112
- for (const {name: queryName, result: query} of result.queries) {
113
- try {
114
- const ast = safeParseQuery(query)
115
- const queryTypes = typeEvaluate(ast, schema)
116
-
117
- const typeName = `${queryName}Result`
118
- const type = typeGenerator.generateTypeNodeTypes(typeName, queryTypes)
119
-
120
- const queryTypeStats = walkAndCountQueryTypeNodeStats(queryTypes)
121
- fileQueryTypes.push({
122
- queryName,
123
- query,
124
- typeName,
125
- typeNode: queryTypes,
126
- type: `${type.trim()}\n`,
127
- unknownTypeNodesGenerated: queryTypeStats.unknownTypes,
128
- typeNodesGenerated: queryTypeStats.allTypes,
129
- emptyUnionTypeNodesGenerated: queryTypeStats.emptyUnions,
130
- })
131
- } catch (err) {
132
- parentPort?.postMessage({
133
- type: 'error',
134
- error: new Error(
135
- `Error generating types for query "${queryName}" in "${result.filename}": ${err.message}`,
136
- {cause: err},
137
- ),
138
- fatal: false,
139
- query,
140
- } satisfies TypegenGenerateTypesWorkerMessage)
102
+ const schema = await readSchema(schemaPath)
103
+ schemas.push({schema, schemaId, filename: schemaPath})
104
+ } catch (err) {
105
+ if (err.code === 'ENOENT') {
106
+ // If the user has not provided a specific schema path (eg we're using the default), give some help
107
+ const hint =
108
+ schemaPath === DEFAULT_SCHEMA_PATH ? ` - did you run "sanity schema extract"?` : ''
109
+ throw new Error(`Schema file not found for schema "${schemaId}": ${schemaPath}${hint}`)
110
+ } else {
111
+ throw err
141
112
  }
142
113
  }
114
+ }
115
+ report.event.loadedSchemas()
143
116
 
144
- if (fileQueryTypes.length > 0) {
145
- $info(`Generated types for ${fileQueryTypes.length} queries in "${result.filename}"\n`)
146
- parentPort?.postMessage({
147
- type: 'types',
148
- types: fileQueryTypes,
149
- filename: result.filename,
150
- } satisfies TypegenGenerateTypesWorkerMessage)
151
- }
117
+ const generator = new TypeGenerator({
118
+ schemas,
119
+ queriesByFile: findQueriesInPath({path: opts.searchPath, resolver: getResolver()}),
120
+ augmentGroqModule: opts.augmentGroqModule,
121
+ overloadClientMethods: opts.overloadClientMethods,
122
+ })
123
+
124
+ report.event.generatedSchemaDeclarations({
125
+ code: [
126
+ generator.getKnownTypes().code,
127
+ ...generator.getSchemaTypeDeclarations().map((i) => i.code),
128
+ generator.getAllSanitySchemaTypesDeclaration().code,
129
+ ...generator.getSchemaDeclarations().map((i) => i.code),
130
+ generator.getAugmentedSchemasDeclarations().code,
131
+ ].join('\n'),
132
+ schemaStats: {
133
+ schemaTypesCount: generator.getSchemaTypeDeclarations().length,
134
+ schemaCount: schemas.length,
135
+ },
136
+ })
152
137
 
153
- if (fileQueryTypes.length > 0) {
154
- allQueries.push(...fileQueryTypes)
138
+ const allFilenames = new Set<string>()
139
+ const errorFilenames = new Set<string>()
140
+ const queryFilenames = new Set<string>()
141
+ const projectionFilenames = new Set<string>()
142
+
143
+ let errorCount = 0
144
+ let queriesCount = 0
145
+ let projectionsCount = 0
146
+ let typeNodesGenerated = 0
147
+ let unknownTypeNodesGenerated = 0
148
+ let emptyUnionTypeNodesGenerated = 0
149
+
150
+ const {fileCount} = await generator.getQueryFileCount()
151
+ report.event.fileCount({fileCount})
152
+
153
+ for await (const {filename, ...result} of generator.getQueryResultDeclarations()) {
154
+ allFilenames.add(filename)
155
+ const progress = {
156
+ queriesCount,
157
+ projectionsCount,
158
+ filesCount: allFilenames.size,
155
159
  }
156
- }
157
160
 
158
- if (opts.overloadClientMethods && allQueries.length > 0) {
159
- const typeMap = `${typeGenerator.generateQueryMap(allQueries).trim()}\n`
160
- parentPort?.postMessage({
161
- type: 'typemap',
162
- typeMap,
163
- } satisfies TypegenGenerateTypesWorkerMessage)
164
- }
161
+ switch (result.type) {
162
+ case 'error': {
163
+ errorCount += 1
164
+ errorFilenames.add(filename)
165
165
 
166
- parentPort?.postMessage({
167
- type: 'complete',
168
- } satisfies TypegenGenerateTypesWorkerMessage)
169
- }
166
+ const errorMessage =
167
+ typeof result.error === 'object' && result.error !== null && 'message' in result.error
168
+ ? String(result.error.message)
169
+ : 'Unknown Error'
170
170
 
171
- function walkAndCountQueryTypeNodeStats(typeNode: TypeNode): {
172
- allTypes: number
173
- unknownTypes: number
174
- emptyUnions: number
175
- } {
176
- switch (typeNode.type) {
177
- case 'unknown': {
178
- return {allTypes: 1, unknownTypes: 1, emptyUnions: 0}
179
- }
180
- case 'array': {
181
- const acc = walkAndCountQueryTypeNodeStats(typeNode.of)
182
- acc.allTypes += 1 // count the array type itself
183
- return acc
184
- }
185
- case 'object': {
186
- // if the rest is unknown, we count it as one unknown type
187
- if (typeNode.rest && typeNode.rest.type === 'unknown') {
188
- return {allTypes: 2, unknownTypes: 1, emptyUnions: 0} // count the object type itself as well
171
+ const message = `Error generating types in "${filename}": ${errorMessage}`
172
+ report.stream.generatedQueryResultDeclaration.emit({type: 'error', message, progress})
173
+ continue
189
174
  }
190
175
 
191
- const restStats = typeNode.rest
192
- ? walkAndCountQueryTypeNodeStats(typeNode.rest)
193
- : {allTypes: 1, unknownTypes: 0, emptyUnions: 0} // count the object type itself
176
+ case 'queries': {
177
+ if (!result.queryResultDeclarations.length) {
178
+ report.stream.generatedQueryResultDeclaration.emit({type: 'progress', progress})
179
+ continue
180
+ }
194
181
 
195
- return Object.values(typeNode.attributes).reduce((acc, attribute) => {
196
- const {allTypes, unknownTypes, emptyUnions} = walkAndCountQueryTypeNodeStats(
197
- attribute.value,
198
- )
199
- return {
200
- allTypes: acc.allTypes + allTypes,
201
- unknownTypes: acc.unknownTypes + unknownTypes,
202
- emptyUnions: acc.emptyUnions + emptyUnions,
182
+ for (const {code, type, stats} of result.queryResultDeclarations) {
183
+ queriesCount += type === 'query' ? 1 : 0
184
+ projectionsCount += type === 'projection' ? 1 : 0
185
+ typeNodesGenerated += stats.allTypes
186
+ unknownTypeNodesGenerated += stats.unknownTypes
187
+ emptyUnionTypeNodesGenerated += stats.emptyUnions
188
+
189
+ if (type === 'projection') {
190
+ projectionFilenames.add(filename)
191
+ } else {
192
+ queryFilenames.add(filename)
193
+ }
194
+
195
+ report.stream.generatedQueryResultDeclaration.emit({type: 'declaration', code, progress})
203
196
  }
204
- }, restStats)
205
- }
206
- case 'union': {
207
- if (typeNode.of.length === 0) {
208
- return {allTypes: 1, unknownTypes: 0, emptyUnions: 1}
197
+ continue
209
198
  }
210
199
 
211
- return typeNode.of.reduce(
212
- (acc, type) => {
213
- const {allTypes, unknownTypes, emptyUnions} = walkAndCountQueryTypeNodeStats(type)
214
- return {
215
- allTypes: acc.allTypes + allTypes,
216
- unknownTypes: acc.unknownTypes + unknownTypes,
217
- emptyUnions: acc.emptyUnions + emptyUnions,
218
- }
219
- },
220
- {allTypes: 1, unknownTypes: 0, emptyUnions: 0}, // count the union type itself
221
- )
222
- }
223
- default: {
224
- return {allTypes: 1, unknownTypes: 0, emptyUnions: 0}
200
+ default: {
201
+ continue
202
+ }
225
203
  }
226
204
  }
205
+ report.stream.generatedQueryResultDeclaration.end()
206
+
207
+ report.event.generationComplete({
208
+ augmentedQueryResultDeclarations: await generator.getAugmentedQueryResultsDeclarations(),
209
+ queryStats: {
210
+ errorCount,
211
+ queriesCount,
212
+ projectionsCount,
213
+ typeNodesGenerated,
214
+ unknownTypeNodesGenerated,
215
+ emptyUnionTypeNodesGenerated,
216
+ totalScannedFilesCount: allFilenames.size,
217
+ filesWithErrors: errorFilenames.size,
218
+ queryFilesCount: queryFilenames.size,
219
+ projectionFilesCount: projectionFilenames.size,
220
+ unknownTypeNodesRatio:
221
+ typeNodesGenerated > 0 ? unknownTypeNodesGenerated / typeNodesGenerated : 0,
222
+ },
223
+ })
227
224
  }
228
225
 
226
+ registerBabel()
229
227
  main()
@@ -23,7 +23,7 @@ export function ExampleComponent() {
23
23
  </Text>
24
24
  <Text muted>
25
25
  Looking for more guidance? See the <a href="https://sanity.io/ui">Sanity UI docs</a>{' '}
26
- and the <a href="https://sdk-docs.sanity.dev">Sanity App SDK docs</a>!
26
+ and the <a href="https://reference.sanity.io/_sanity/sdk-react/">Sanity App SDK docs</a>!
27
27
  </Text>
28
28
  </Stack>
29
29
  </Flex>