@sanity/cli 3.86.1 → 3.86.2-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.
@@ -1,15 +1,18 @@
1
+ /* eslint-disable complexity */
2
+ /* eslint-disable max-statements */
3
+ /* eslint-disable max-depth */
1
4
  import {constants, mkdir, open, stat} from 'node:fs/promises'
2
5
  import {dirname, join} from 'node:path'
3
6
  import {Worker} from 'node:worker_threads'
4
7
 
5
- import {readConfig} from '@sanity/codegen'
6
- import {format as prettierFormat, resolveConfig as resolvePrettierConfig} from 'prettier'
8
+ import {readConfig, type TypeEvaluationStats} from '@sanity/codegen'
7
9
 
8
10
  import {type CliCommandArguments, type CliCommandContext} from '../../types'
9
11
  import {getCliWorkerPath} from '../../util/cliWorker'
12
+ import {createReceiver} from '../../util/workerChannel'
10
13
  import {
11
14
  type TypegenGenerateTypesWorkerData,
12
- type TypegenGenerateTypesWorkerMessage,
15
+ type TypegenWorkerChannel,
13
16
  } from '../../workers/typegenGenerate'
14
17
  import {TypesGeneratedTrace} from './generate.telemetry'
15
18
 
@@ -31,6 +34,51 @@ const generatedFileWarning = `/**
31
34
  * ---------------------------------------------------------------------------------
32
35
  */\n\n`
33
36
 
37
+ /**
38
+ * Helper function to format the generated file using Prettier.
39
+ * Handles its own file operations and error reporting.
40
+ */
41
+ async function formatGeneratedFile(
42
+ outputPath: string,
43
+ output: CliCommandContext['output'],
44
+ spinner: ReturnType<CliCommandContext['output']['spinner']>,
45
+ ): Promise<void> {
46
+ // this is here because this is an ESM-only import
47
+ const {format: prettierFormat, resolveConfig: resolvePrettierConfig} = await import('prettier')
48
+
49
+ let formatFile
50
+ try {
51
+ // Load prettier config
52
+ const prettierConfig = await resolvePrettierConfig(outputPath).catch((err) => {
53
+ output.warn(`Failed to load prettier config: ${err.message}`)
54
+ return null
55
+ })
56
+
57
+ if (prettierConfig) {
58
+ spinner.text = 'Formatting generated types with Prettier...'
59
+ formatFile = await open(outputPath, constants.O_RDWR)
60
+ try {
61
+ const code = await formatFile.readFile()
62
+ const formattedCode = await prettierFormat(code.toString(), {
63
+ ...prettierConfig,
64
+ parser: 'typescript' as const,
65
+ })
66
+ await formatFile.truncate() // Truncate before writing formatted code
67
+ await formatFile.write(formattedCode, 0) // Write formatted code from the beginning
68
+ spinner.info('Formatted generated types with Prettier')
69
+ } catch (err) {
70
+ output.warn(`Failed to format generated types with Prettier: ${err.message}`)
71
+ } finally {
72
+ // Ensure the formatting file handle is closed
73
+ await formatFile?.close()
74
+ }
75
+ }
76
+ } catch (err) {
77
+ // Catch errors during the formatting setup (e.g., opening the formatFile)
78
+ output.warn(`Error during formatting setup: ${err.message}`)
79
+ }
80
+ }
81
+
34
82
  export default async function typegenGenerateAction(
35
83
  args: CliCommandArguments<TypegenGenerateTypesCommandFlags>,
36
84
  context: CliCommandContext,
@@ -41,170 +89,157 @@ export default async function typegenGenerateAction(
41
89
  const trace = telemetry.trace(TypesGeneratedTrace)
42
90
  trace.start()
43
91
 
44
- const codegenConfig = await readConfig(flags['config-path'] || 'sanity-typegen.json')
92
+ const typegenConfig = await readConfig(flags['config-path'] ?? 'sanity-typegen.json')
45
93
 
46
- try {
47
- const schemaStats = await stat(codegenConfig.schema)
48
- if (!schemaStats.isFile()) {
49
- throw new Error(`Schema path is not a file: ${codegenConfig.schema}`)
94
+ const missingSchemas: string[] = []
95
+ const invalidSchemas: string[] = []
96
+
97
+ for (const schemaPath of typegenConfig.schemas.map((i) => i.schemaPath)) {
98
+ try {
99
+ const schemaStats = await stat(schemaPath)
100
+ if (!schemaStats.isFile()) {
101
+ invalidSchemas.push(schemaPath)
102
+ }
103
+ } catch (err) {
104
+ if (err.code === 'ENOENT') {
105
+ missingSchemas.push(schemaPath)
106
+ } else {
107
+ throw err
108
+ }
50
109
  }
51
- } catch (err) {
52
- if (err.code === 'ENOENT') {
110
+ }
111
+
112
+ if (missingSchemas.length > 0 || invalidSchemas.length > 0) {
113
+ const errors: string[] = []
114
+
115
+ if (missingSchemas.length > 0) {
53
116
  // If the user has not provided a specific schema path (eg we're using the default), give some help
54
117
  const hint =
55
- codegenConfig.schema === './schema.json' ? ` - did you run "sanity schema extract"?` : ''
56
- throw new Error(`Schema file not found: ${codegenConfig.schema}${hint}`)
118
+ missingSchemas.length === 1 && missingSchemas[0] === './schema.json'
119
+ ? ' - did you run "sanity schema extract"?'
120
+ : ''
121
+ const schemaList = missingSchemas.map((path) => ` - ${path}`).join('\n')
122
+ errors.push(`The following schema files were not found:\n${schemaList}${hint}`)
123
+ }
124
+
125
+ if (invalidSchemas.length > 0) {
126
+ const schemaList = invalidSchemas.map((path) => ` - ${path}`).join('\n')
127
+ errors.push(`The following schema paths are not files:\n${schemaList}`)
57
128
  }
58
- throw err
129
+
130
+ throw new Error(errors.join('\n\n'))
59
131
  }
60
132
 
61
- const outputPath = join(process.cwd(), codegenConfig.generates)
133
+ const outputPath = join(process.cwd(), typegenConfig.generates)
62
134
  const outputDir = dirname(outputPath)
63
135
  await mkdir(outputDir, {recursive: true})
64
136
  const workerPath = await getCliWorkerPath('typegenGenerate')
65
137
 
66
- const spinner = output.spinner({}).start('Generating types')
138
+ const spinner = output.spinner('Generating types')
67
139
 
68
140
  const worker = new Worker(workerPath, {
69
141
  workerData: {
70
142
  workDir,
71
- schemaPath: codegenConfig.schema,
72
- searchPath: codegenConfig.path,
73
- overloadClientMethods: codegenConfig.overloadClientMethods,
143
+ schemas: typegenConfig.schemas,
144
+ searchPath: typegenConfig.path,
145
+ overloadClientMethods: typegenConfig.overloadClientMethods,
146
+ augmentGroqModule: typegenConfig.augmentGroqModule,
74
147
  } satisfies TypegenGenerateTypesWorkerData,
75
148
  // eslint-disable-next-line no-process-env
76
149
  env: process.env,
77
150
  })
78
151
 
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)
152
+ const receiver = createReceiver<TypegenWorkerChannel>(worker)
86
153
 
154
+ let fileHandle
87
155
  const stats = {
88
156
  queryFilesCount: 0,
89
- errors: 0,
90
157
  queriesCount: 0,
158
+ projectionsCount: 0,
91
159
  schemaTypesCount: 0,
92
- unknownTypeNodesGenerated: 0,
93
- typeNodesGenerated: 0,
94
- emptyUnionTypeNodesGenerated: 0,
95
- size: 0,
160
+ typeEvaluationStats: null as TypeEvaluationStats | null,
161
+ outputSize: 0,
162
+ filesWithErrors: 0,
96
163
  }
97
164
 
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
112
- }
113
- if (msg.type === 'complete') {
114
- resolve()
115
- return
116
- }
117
-
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
124
- }
165
+ try {
166
+ try {
167
+ spinner.start()
125
168
 
126
- let fileTypeString = `// Source: ${msg.filename}\n`
169
+ fileHandle = await open(outputPath, 'w')
170
+ await fileHandle.write(generatedFileWarning)
127
171
 
128
- if (msg.type === 'schema') {
129
- stats.schemaTypesCount += msg.length
130
- fileTypeString += msg.schema
131
- typeFile.write(fileTypeString)
132
- return
133
- }
172
+ spinner.text = 'Generating schema types...'
173
+ const {code: schemaCode, schemas} = await receiver.event.schema()
174
+ stats.schemaTypesCount = schemas.reduce((total, schema) => total + schema.typeCount, 0)
175
+ await fileHandle.write(schemaCode)
134
176
 
135
- if (msg.type === 'types') {
177
+ spinner.text = 'Generating query types...'
178
+ for await (const queryResult of receiver.stream.queries()) {
136
179
  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
180
+ const {error, results} = queryResult
181
+ if (error) {
182
+ stats.filesWithErrors++
183
+ }
184
+ for (const result of results) {
185
+ await fileHandle.write(result.code)
186
+ if (result.type === 'projection') {
187
+ stats.projectionsCount++
188
+ } else {
189
+ stats.queriesCount++
190
+ }
152
191
  }
153
- typeFile.write(`${fileTypeString}\n`)
154
- stats.size += Buffer.byteLength(fileTypeString)
155
192
  }
156
- })
157
- worker.addListener('error', reject)
158
- })
159
-
160
- await typeFile.close()
161
193
 
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}`)
194
+ spinner.text = 'Generating typemap...'
195
+ const {code: typemapCode, stats: finalStats} = await receiver.event.typemap()
196
+ stats.typeEvaluationStats = finalStats
197
+ await fileHandle.write(typemapCode)
183
198
  } finally {
184
- await formatFile.close()
199
+ // Ensure the initial file handle is closed before moving on
200
+ await fileHandle?.close()
185
201
  }
186
- }
187
202
 
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
- })
203
+ if (typegenConfig.formatGeneratedCode) {
204
+ await formatGeneratedFile(outputPath, output, spinner)
205
+ }
201
206
 
202
- trace.complete()
203
- if (stats.errors > 0) {
204
- spinner.warn(`Encountered errors in ${stats.errors} files while generating types`)
205
- }
207
+ // Gather final stats and report success
208
+ const finalStat = await stat(outputPath)
209
+ stats.outputSize = finalStat.size
210
+
211
+ trace.log({
212
+ outputSize: stats.outputSize,
213
+ queriesCount: stats.queriesCount,
214
+ projectionsCount: stats.projectionsCount,
215
+ schemaTypesCount: stats.schemaTypesCount,
216
+ queryFilesCount: stats.queryFilesCount,
217
+ filesWithErrors: stats.filesWithErrors,
218
+ typeNodesGenerated: stats.typeEvaluationStats?.totalTypeNodes,
219
+ unknownTypeNodesGenerated: stats.typeEvaluationStats?.unknownTypeCount,
220
+ unknownTypeNodesRatio:
221
+ stats.typeEvaluationStats && stats.typeEvaluationStats.totalTypeNodes > 0
222
+ ? stats.typeEvaluationStats.unknownTypeCount / stats.typeEvaluationStats.totalTypeNodes
223
+ : 0,
224
+ emptyUnionTypeNodesGenerated: stats.typeEvaluationStats?.emptyUnionCount,
225
+ configOverloadClientMethods: typegenConfig.overloadClientMethods,
226
+ configAugmentGroqModule: typegenConfig.augmentGroqModule,
227
+ })
228
+
229
+ trace.complete()
230
+ if (stats.filesWithErrors > 0) {
231
+ spinner.warn(`Encountered errors in ${stats.filesWithErrors} files while generating types`)
232
+ }
206
233
 
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
- )
234
+ spinner.succeed(
235
+ `Generated TypeScript types for ${stats.schemaTypesCount} schema types, ${stats.queriesCount} GROQ queries, ${stats.projectionsCount} GROQ projections, in ${stats.queryFilesCount} files into: ${typegenConfig.generates}`,
236
+ )
237
+ } catch (err) {
238
+ spinner.fail('Type generation failed')
239
+ trace.error(err instanceof Error ? err : new Error(String(err)))
240
+ throw err // Re-throw the error after logging
241
+ } finally {
242
+ // Ensure the worker receiver is always disposed
243
+ await receiver.dispose()
244
+ }
210
245
  }
package/src/cli.ts CHANGED
File without changes
File without changes
File without changes
@@ -0,0 +1,222 @@
1
+ import {EventEmitter} from 'node:events'
2
+ import {type MessagePort, type Worker} from 'node:worker_threads'
3
+
4
+ import {afterEach, beforeEach, describe, expect, it, vi} from 'vitest'
5
+
6
+ import {
7
+ createReceiver,
8
+ createReporter,
9
+ type WorkerChannel,
10
+ type WorkerChannelEvent,
11
+ type WorkerChannelStream,
12
+ } from '../workerChannel'
13
+
14
+ // Define a sample worker channel for testing
15
+ type TestWorkerChannel = WorkerChannel<{
16
+ simpleEvent: WorkerChannelEvent<string>
17
+ dataEvent: WorkerChannelEvent<{id: number; value: boolean}>
18
+ simpleStream: WorkerChannelStream<number>
19
+ endStream: WorkerChannelStream<void>
20
+ }>
21
+
22
+ // Mock Worker and MessagePort
23
+ class MockWorker extends EventEmitter {
24
+ terminated = false
25
+ terminate = vi.fn(async () => {
26
+ this.terminated = true
27
+ return 0
28
+ })
29
+ postMessage = vi.fn((message: unknown) => {
30
+ this.emit('message', message)
31
+ })
32
+
33
+ // Helper to simulate receiving a message from the parent (if needed)
34
+ receiveMessage(message: unknown) {
35
+ this.emit('message', message)
36
+ }
37
+
38
+ // Helper to simulate an error from the worker
39
+ emitError(error: unknown) {
40
+ this.emit('error', error)
41
+ }
42
+ }
43
+
44
+ class MockMessagePort extends EventEmitter {
45
+ postMessage = vi.fn((message: unknown) => {
46
+ // Simulate the message being sent back to the parent/receiver
47
+ // In a real scenario, this would go to the Worker's listener
48
+ mockWorkerInstance?.receiveMessage(message)
49
+ })
50
+
51
+ // Helper to simulate receiving a message (e.g., from the parent)
52
+ receiveMessage(message: unknown) {
53
+ this.emit('message', message)
54
+ }
55
+ }
56
+
57
+ let mockWorkerInstance: MockWorker
58
+ let mockParentPortInstance: MockMessagePort
59
+ let receiver: ReturnType<typeof createReceiver<TestWorkerChannel>>
60
+ let reporter: ReturnType<typeof createReporter<TestWorkerChannel>>
61
+
62
+ beforeEach(() => {
63
+ mockWorkerInstance = new MockWorker()
64
+ mockParentPortInstance = new MockMessagePort()
65
+ receiver = createReceiver<TestWorkerChannel>(mockWorkerInstance as unknown as Worker)
66
+ reporter = createReporter<TestWorkerChannel>(mockParentPortInstance as unknown as MessagePort)
67
+ })
68
+
69
+ afterEach(() => {
70
+ vi.clearAllMocks()
71
+ })
72
+
73
+ describe('workerChannel', () => {
74
+ it('should send and receive a simple event', async () => {
75
+ const receivedPromise = receiver.event.simpleEvent()
76
+ reporter.event.simpleEvent('hello')
77
+
78
+ await expect(receivedPromise).resolves.toBe('hello')
79
+ })
80
+
81
+ it('should send and receive an event with data object', async () => {
82
+ const payload = {id: 123, value: true}
83
+ const receivedPromise = receiver.event.dataEvent()
84
+ reporter.event.dataEvent(payload)
85
+
86
+ await expect(receivedPromise).resolves.toEqual(payload)
87
+ })
88
+
89
+ it('should send and receive a stream of data', async () => {
90
+ const receivedItems: number[] = []
91
+ const streamPromise = (async () => {
92
+ for await (const item of receiver.stream.simpleStream()) {
93
+ receivedItems.push(item)
94
+ }
95
+ })()
96
+
97
+ reporter.stream.simpleStream.emit(1)
98
+ reporter.stream.simpleStream.emit(2)
99
+ reporter.stream.simpleStream.emit(3)
100
+ reporter.stream.simpleStream.end()
101
+
102
+ await streamPromise // Wait for the stream processing to complete
103
+
104
+ expect(receivedItems).toEqual([1, 2, 3])
105
+ })
106
+
107
+ it('should handle an empty stream correctly', async () => {
108
+ let streamEntered = false
109
+ const streamPromise = (async () => {
110
+ for await (const _item of receiver.stream.endStream()) {
111
+ streamEntered = true // This should not happen
112
+ }
113
+ })()
114
+
115
+ reporter.stream.endStream.end() // End immediately
116
+
117
+ await streamPromise
118
+
119
+ expect(streamEntered).toBe(false)
120
+ })
121
+
122
+ it('should propagate errors from the worker via event receiver', async () => {
123
+ const error = new Error('Worker failed')
124
+
125
+ const receivedPromise = receiver.event.simpleEvent()
126
+ mockWorkerInstance?.emitError(error)
127
+
128
+ await expect(receivedPromise).rejects.toThrow(error)
129
+ })
130
+
131
+ it('should propagate errors from the worker via stream receiver', async () => {
132
+ const error = new Error('Worker failed during stream')
133
+
134
+ const streamPromise = (async () => {
135
+ // eslint-disable-next-line @typescript-eslint/no-unused-vars
136
+ for await (const _item of receiver.stream.simpleStream()) {
137
+ // no-op
138
+ }
139
+ })()
140
+
141
+ // Emit error before ending the stream
142
+ mockWorkerInstance?.emitError(error)
143
+
144
+ await expect(streamPromise).rejects.toThrow(error)
145
+ })
146
+
147
+ it('should handle messages arriving before receiver awaits', async () => {
148
+ // Reporter sends event *before* receiver awaits it
149
+ reporter.event.simpleEvent('early bird')
150
+
151
+ // Give a tick for the message to be processed internally by the mock
152
+ await new Promise((resolve) => setImmediate(resolve))
153
+
154
+ const receivedPromise = receiver.event.simpleEvent()
155
+
156
+ await expect(receivedPromise).resolves.toBe('early bird')
157
+ })
158
+
159
+ it('should handle stream emissions arriving before receiver iterates', async () => {
160
+ // Reporter sends stream data *before* receiver starts iterating
161
+ reporter.stream.simpleStream.emit(10)
162
+ reporter.stream.simpleStream.emit(20)
163
+
164
+ // Give a tick for messages to process
165
+ await new Promise((resolve) => setImmediate(resolve))
166
+
167
+ const receivedItems: number[] = []
168
+ const streamPromise = (async () => {
169
+ for await (const item of receiver.stream.simpleStream()) {
170
+ receivedItems.push(item)
171
+ }
172
+ })()
173
+
174
+ // Send remaining data and end
175
+ reporter.stream.simpleStream.emit(30)
176
+ reporter.stream.simpleStream.end()
177
+
178
+ await streamPromise
179
+
180
+ expect(receivedItems).toEqual([10, 20, 30])
181
+ })
182
+
183
+ it('dispose() should remove listeners and terminate worker', async () => {
184
+ expect(mockWorkerInstance?.listenerCount('message')).toBe(1)
185
+ expect(mockWorkerInstance?.listenerCount('error')).toBe(1)
186
+
187
+ const terminatePromise = receiver.dispose()
188
+
189
+ await expect(terminatePromise).resolves.toBe(0)
190
+ expect(mockWorkerInstance?.terminate).toHaveBeenCalledTimes(1)
191
+ expect(mockWorkerInstance?.listenerCount('message')).toBe(0)
192
+ expect(mockWorkerInstance?.listenerCount('error')).toBe(0)
193
+ expect(mockWorkerInstance?.terminated).toBe(true)
194
+ })
195
+
196
+ it('should throw error if parentPort is null for reporter', () => {
197
+ expect(() => createReporter<TestWorkerChannel>(null)).toThrow('parentPart was falsy')
198
+ })
199
+
200
+ it('should ignore non-worker channel messages', async () => {
201
+ const receivedPromise = receiver.event.simpleEvent()
202
+
203
+ // Send a valid message
204
+ reporter.event.simpleEvent('valid')
205
+ await expect(receivedPromise).resolves.toBe('valid')
206
+
207
+ const nextReceivedPromise = receiver.event.simpleEvent()
208
+
209
+ // Send an invalid message
210
+ mockWorkerInstance?.receiveMessage({foo: 'bar'}) // Not a valid WorkerChannelMessage
211
+ mockWorkerInstance?.receiveMessage('just a string')
212
+ mockWorkerInstance?.receiveMessage(null)
213
+ mockWorkerInstance?.receiveMessage(undefined)
214
+ mockWorkerInstance?.receiveMessage({type: 'unknown'})
215
+
216
+ // Send the actual message we are waiting for
217
+ reporter.event.simpleEvent('after invalid')
218
+
219
+ // It should eventually resolve with the correct message, ignoring the invalid ones
220
+ await expect(nextReceivedPromise).resolves.toBe('after invalid')
221
+ })
222
+ })
@@ -7,7 +7,6 @@ import {
7
7
  type DocumentDefinition,
8
8
  type ObjectDefinition,
9
9
  } from '@sanity/types'
10
- import {format} from 'prettier'
11
10
 
12
11
  import {type CliApiClient} from '../types'
13
12
  import {getCliWorkerPath} from './cliWorker'
@@ -184,6 +183,7 @@ async function fetchJourneySchema(schemaUrl: string): Promise<DocumentOrObject[]
184
183
  async function assembleJourneySchemaTypeFileContent(schemaType: DocumentOrObject): Promise<string> {
185
184
  const serialised = wrapSchemaTypeInHelpers(schemaType)
186
185
  const imports = getImports(serialised)
186
+ const {format} = await import('prettier')
187
187
  const prettifiedSchemaType = await format(serialised, {
188
188
  parser: 'typescript',
189
189
  printWidth: 40,
@@ -198,11 +198,12 @@ async function assembleJourneySchemaTypeFileContent(schemaType: DocumentOrObject
198
198
  * @param schemas - The Journey schemas to assemble into an index file
199
199
  * @returns The index file as a string
200
200
  */
201
- function assembleJourneyIndexContent(schemas: DocumentOrObject[]): Promise<string> {
201
+ async function assembleJourneyIndexContent(schemas: DocumentOrObject[]): Promise<string> {
202
202
  const sortedSchema = schemas.slice().sort((a, b) => (a.name > b.name ? 1 : -1))
203
203
  const imports = sortedSchema.map((schema) => `import { ${schema.name} } from './${schema.name}'`)
204
204
  const exports = sortedSchema.map((schema) => schema.name).join(',')
205
205
  const fileContents = `${imports.join('\n')}\n\nexport const schemaTypes = [${exports}]`
206
+ const {format} = await import('prettier')
206
207
  return format(fileContents, {parser: 'typescript'})
207
208
  }
208
209