@sanity/cli 3.86.0 → 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.
- package/lib/_chunks-cjs/cli.js +30 -34
- package/lib/_chunks-cjs/cli.js.map +1 -1
- package/lib/_chunks-cjs/generateAction.js +118 -96
- package/lib/_chunks-cjs/generateAction.js.map +1 -1
- package/lib/_chunks-cjs/journeyConfig.js +24 -5
- package/lib/_chunks-cjs/journeyConfig.js.map +1 -1
- package/lib/_chunks-cjs/loadEnv.js +3 -3
- package/lib/_chunks-cjs/loadEnv.js.map +1 -1
- package/lib/_chunks-cjs/workerChannel.js +84 -0
- package/lib/_chunks-cjs/workerChannel.js.map +1 -0
- package/lib/workers/typegenGenerate.d.ts +104 -36
- package/lib/workers/typegenGenerate.js +24 -111
- package/lib/workers/typegenGenerate.js.map +1 -1
- package/package.json +18 -20
- package/src/actions/typegen/generate.telemetry.ts +5 -3
- package/src/actions/typegen/generateAction.ts +165 -130
- package/src/cli.ts +0 -0
- package/src/commands/functions/envFunctionsCommand.ts +15 -12
- package/src/commands/functions/logsFunctionsCommand.ts +9 -6
- package/src/commands/functions/testFunctionsCommand.ts +2 -3
- package/src/commands/projects/listProjectsCommand.ts +0 -0
- package/src/commands/projects/projectsGroup.ts +0 -0
- package/src/util/__tests__/workerChannel.test.ts +222 -0
- package/src/util/journeyConfig.ts +3 -2
- package/src/util/workerChannel.ts +312 -0
- package/src/workers/typegenGenerate.ts +55 -193
@@ -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
|
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
|
92
|
+
const typegenConfig = await readConfig(flags['config-path'] ?? 'sanity-typegen.json')
|
45
93
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
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
|
-
}
|
52
|
-
|
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
|
-
|
56
|
-
|
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
|
-
|
129
|
+
|
130
|
+
throw new Error(errors.join('\n\n'))
|
59
131
|
}
|
60
132
|
|
61
|
-
const outputPath = join(process.cwd(),
|
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(
|
138
|
+
const spinner = output.spinner('Generating types')
|
67
139
|
|
68
140
|
const worker = new Worker(workerPath, {
|
69
141
|
workerData: {
|
70
142
|
workDir,
|
71
|
-
|
72
|
-
searchPath:
|
73
|
-
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
|
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
|
-
|
93
|
-
|
94
|
-
|
95
|
-
size: 0,
|
160
|
+
typeEvaluationStats: null as TypeEvaluationStats | null,
|
161
|
+
outputSize: 0,
|
162
|
+
filesWithErrors: 0,
|
96
163
|
}
|
97
164
|
|
98
|
-
|
99
|
-
|
100
|
-
|
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
|
-
|
169
|
+
fileHandle = await open(outputPath, 'w')
|
170
|
+
await fileHandle.write(generatedFileWarning)
|
127
171
|
|
128
|
-
|
129
|
-
|
130
|
-
|
131
|
-
|
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
|
-
|
177
|
+
spinner.text = 'Generating query types...'
|
178
|
+
for await (const queryResult of receiver.stream.queries()) {
|
136
179
|
stats.queryFilesCount++
|
137
|
-
|
138
|
-
|
139
|
-
|
140
|
-
|
141
|
-
|
142
|
-
|
143
|
-
|
144
|
-
|
145
|
-
|
146
|
-
|
147
|
-
|
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
|
-
|
163
|
-
|
164
|
-
|
165
|
-
|
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
|
-
|
199
|
+
// Ensure the initial file handle is closed before moving on
|
200
|
+
await fileHandle?.close()
|
185
201
|
}
|
186
|
-
}
|
187
202
|
|
188
|
-
|
189
|
-
|
190
|
-
|
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
|
-
|
203
|
-
|
204
|
-
|
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
|
-
|
208
|
-
|
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
|
@@ -5,25 +5,25 @@ import {type CliCommandDefinition} from '../../types'
|
|
5
5
|
type StackFunctionResource = types.StackFunctionResource
|
6
6
|
|
7
7
|
const helpText = `
|
8
|
+
Arguments
|
9
|
+
[add] Add or update an environment variable
|
10
|
+
[remove] Remove an environment variable
|
11
|
+
|
8
12
|
Options
|
9
13
|
--name <name> The name of the function
|
10
|
-
--add Add or update an environment variable
|
11
|
-
--remove Remove an environment variable
|
12
14
|
--key <key> The name of the environment variable
|
13
15
|
--value <value> The value of the environment variable
|
14
16
|
|
15
17
|
Examples
|
16
18
|
# Add or update an environment variable
|
17
|
-
sanity functions env --name echo --
|
19
|
+
sanity functions env add --name echo --key API_URL --value https://api.example.com/
|
18
20
|
|
19
21
|
# Remove an environment variable
|
20
|
-
sanity functions env --name echo --
|
22
|
+
sanity functions env remove --name echo --key API_URL
|
21
23
|
`
|
22
24
|
|
23
25
|
const defaultFlags = {
|
24
26
|
name: '',
|
25
|
-
add: false,
|
26
|
-
remove: false,
|
27
27
|
key: '',
|
28
28
|
value: '',
|
29
29
|
}
|
@@ -38,16 +38,20 @@ const envFunctionsCommand: CliCommandDefinition = {
|
|
38
38
|
async action(args, context) {
|
39
39
|
const {apiClient, output} = context
|
40
40
|
const {print} = output
|
41
|
+
const [subCommand] = args.argsWithoutOptions
|
41
42
|
const flags = {...defaultFlags, ...args.extOptions}
|
42
43
|
|
44
|
+
if (!subCommand || !['add', 'remove'].includes(subCommand)) {
|
45
|
+
throw new Error('You must specify if you wish to add or remove an environment variable')
|
46
|
+
}
|
47
|
+
|
43
48
|
const client = apiClient({
|
44
49
|
requireUser: true,
|
45
50
|
requireProject: false,
|
46
51
|
})
|
47
52
|
|
48
53
|
if (flags.name === '') {
|
49
|
-
|
50
|
-
return
|
54
|
+
throw new Error('You must provide a function name via the --name flag')
|
51
55
|
}
|
52
56
|
|
53
57
|
const token = client.config().token
|
@@ -61,8 +65,7 @@ const envFunctionsCommand: CliCommandDefinition = {
|
|
61
65
|
})
|
62
66
|
|
63
67
|
if (!deployedStack) {
|
64
|
-
|
65
|
-
return
|
68
|
+
throw new Error('Stack not found')
|
66
69
|
}
|
67
70
|
|
68
71
|
const blueprintConfig = blueprint.readConfigFile()
|
@@ -74,7 +77,7 @@ const envFunctionsCommand: CliCommandDefinition = {
|
|
74
77
|
) as StackFunctionResource
|
75
78
|
|
76
79
|
if (token && projectId) {
|
77
|
-
if (
|
80
|
+
if (subCommand === 'add') {
|
78
81
|
print(`Updating "${flags.key}" environment variable in "${flags.name}"`)
|
79
82
|
const result = await env.update.update(externalId, flags.key, flags.value, {
|
80
83
|
token,
|
@@ -86,7 +89,7 @@ const envFunctionsCommand: CliCommandDefinition = {
|
|
86
89
|
print(`Failed to update ${flags.key}`)
|
87
90
|
print(`Error: ${result.error || 'Unknown error'}`)
|
88
91
|
}
|
89
|
-
} else if (
|
92
|
+
} else if (subCommand === 'remove') {
|
90
93
|
print(`Removing "${flags.key}" environment variable in "${flags.name}"`)
|
91
94
|
const result = await env.remove.remove(externalId, flags.key, {
|
92
95
|
token,
|
@@ -7,8 +7,9 @@ type StackFunctionResource = types.StackFunctionResource
|
|
7
7
|
const helpText = `
|
8
8
|
Options
|
9
9
|
--name <name> The name of the function to retrieve logs for
|
10
|
-
--limit <limit> The number of log entries to retrieve
|
10
|
+
--limit <limit> The number of log entries to retrieve [default 50]
|
11
11
|
--json If set return json
|
12
|
+
--utc Use UTC dates in logs
|
12
13
|
|
13
14
|
Examples
|
14
15
|
# Retrieve logs for Sanity Function abcd1234
|
@@ -25,6 +26,7 @@ const defaultFlags = {
|
|
25
26
|
name: '',
|
26
27
|
limit: 50,
|
27
28
|
json: false,
|
29
|
+
utc: false,
|
28
30
|
}
|
29
31
|
|
30
32
|
const logsFunctionsCommand: CliCommandDefinition = {
|
@@ -45,8 +47,7 @@ const logsFunctionsCommand: CliCommandDefinition = {
|
|
45
47
|
})
|
46
48
|
|
47
49
|
if (flags.name === '') {
|
48
|
-
|
49
|
-
return
|
50
|
+
throw new Error('You must provide a function name via the --name flag')
|
50
51
|
}
|
51
52
|
|
52
53
|
const token = client.config().token
|
@@ -59,8 +60,7 @@ const logsFunctionsCommand: CliCommandDefinition = {
|
|
59
60
|
})
|
60
61
|
|
61
62
|
if (!deployedStack) {
|
62
|
-
|
63
|
-
return
|
63
|
+
throw new Error('Stack not found')
|
64
64
|
}
|
65
65
|
|
66
66
|
const blueprintConfig = blueprint.readConfigFile()
|
@@ -105,7 +105,10 @@ const logsFunctionsCommand: CliCommandDefinition = {
|
|
105
105
|
for (const log of filteredLogs) {
|
106
106
|
const {time, level, message} = log
|
107
107
|
const date = new Date(time)
|
108
|
-
|
108
|
+
const dateString = flags.utc
|
109
|
+
? date.toISOString().slice(0, 19).split('T').join(' ')
|
110
|
+
: `${date.toLocaleDateString()} ${date.toLocaleTimeString()}`
|
111
|
+
print(`${dateString} ${level} ${message}`)
|
109
112
|
}
|
110
113
|
}
|
111
114
|
} else {
|
@@ -38,8 +38,7 @@ const testFunctionsCommand: CliCommandDefinition = {
|
|
38
38
|
const flags = {...defaultFlags, ...args.extOptions}
|
39
39
|
|
40
40
|
if (flags.name === '') {
|
41
|
-
|
42
|
-
return
|
41
|
+
throw new Error('You must provide a function name via the --name flag')
|
43
42
|
}
|
44
43
|
|
45
44
|
const {test} = await import('@sanity/runtime-cli/actions/functions')
|
@@ -52,7 +51,7 @@ const testFunctionsCommand: CliCommandDefinition = {
|
|
52
51
|
|
53
52
|
const src = findFunction.getFunctionSource(parsedBlueprint, flags.name)
|
54
53
|
if (!src) {
|
55
|
-
|
54
|
+
throw new Error(`Error: Function ${flags.name} has no source code`)
|
56
55
|
}
|
57
56
|
|
58
57
|
const {json, logs, error} = await test.testAction(
|
File without changes
|
File without changes
|