@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,15 +1,17 @@
1
+ /* eslint-disable max-statements */
1
2
  import {constants, mkdir, open, stat} from 'node:fs/promises'
2
3
  import {dirname, join} from 'node:path'
4
+ import process from 'node:process'
3
5
  import {Worker} from 'node:worker_threads'
4
6
 
5
- import {readConfig} from '@sanity/codegen'
6
- import {format as prettierFormat, resolveConfig as resolvePrettierConfig} from 'prettier'
7
+ import {DEFAULT_CONFIG, readConfig} from '@sanity/codegen'
7
8
 
8
9
  import {type CliCommandArguments, type CliCommandContext} from '../../types'
9
10
  import {getCliWorkerPath} from '../../util/cliWorker'
11
+ import {createReceiver} from '../../util/workerChannel'
10
12
  import {
11
13
  type TypegenGenerateTypesWorkerData,
12
- type TypegenGenerateTypesWorkerMessage,
14
+ type TypegenWorkerChannel,
13
15
  } from '../../workers/typegenGenerate'
14
16
  import {TypesGeneratedTrace} from './generate.telemetry'
15
17
 
@@ -17,7 +19,8 @@ export interface TypegenGenerateTypesCommandFlags {
17
19
  'config-path'?: string
18
20
  }
19
21
 
20
- const generatedFileWarning = `/**
22
+ const DEFAULT_CONFIG_PATH = 'sanity-typegen.json'
23
+ const GENERATED_FILE_WARNING = `/**
21
24
  * ---------------------------------------------------------------------------------
22
25
  * This file has been generated by Sanity TypeGen.
23
26
  * Command: \`sanity typegen generate\`
@@ -31,6 +34,20 @@ const generatedFileWarning = `/**
31
34
  * ---------------------------------------------------------------------------------
32
35
  */\n\n`
33
36
 
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
+
34
51
  export default async function typegenGenerateAction(
35
52
  args: CliCommandArguments<TypegenGenerateTypesCommandFlags>,
36
53
  context: CliCommandContext,
@@ -41,170 +58,160 @@ export default async function typegenGenerateAction(
41
58
  const trace = telemetry.trace(TypesGeneratedTrace)
42
59
  trace.start()
43
60
 
44
- const codegenConfig = await readConfig(flags['config-path'] || 'sanity-typegen.json')
61
+ const spinner = output.spinner('Generating types…')
62
+ spinner.start()
45
63
 
64
+ let codegenConfig
65
+ const configPath = flags['config-path'] ?? DEFAULT_CONFIG_PATH
46
66
  try {
47
- const schemaStats = await stat(codegenConfig.schema)
48
- if (!schemaStats.isFile()) {
49
- throw new Error(`Schema path is not a file: ${codegenConfig.schema}`)
67
+ codegenConfig = await readConfig(configPath)
68
+ spinner.info(`Using typegen configuration found at "${configPath}"`)
69
+ } catch (error) {
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
+ )
50
77
  }
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
59
78
  }
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
79
+ const {
80
+ schemas,
81
+ augmentGroqModule,
82
+ formatGeneratedCode,
83
+ generates,
84
+ overloadClientMethods,
85
+ path: searchPath,
86
+ } = codegenConfig
87
+
88
+ spinner.start(`Loading schema${schemas.length === 1 ? '' : 's'}…`)
89
+
90
+ const outputPath = join(process.cwd(), generates)
91
+ await mkdir(dirname(outputPath), {recursive: true})
92
+
93
+ const workerData: TypegenGenerateTypesWorkerData = {
94
+ workDir,
95
+ schemas,
96
+ searchPath,
97
+ overloadClientMethods,
98
+ augmentGroqModule,
99
+ }
100
+ const worker = new Worker(await getCliWorkerPath('typegenGenerate'), {
101
+ workerData,
76
102
  env: process.env,
77
103
  })
104
+ const receiver = createReceiver<TypegenWorkerChannel>(worker)
78
105
 
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
- }
106
+ let fileHandle
107
+ let schemaStats
108
+ let queryStats
97
109
 
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
110
+ try {
111
+ await receiver.event.loadedSchemas()
112
+ spinner.succeed(
113
+ schemas.length === 1
114
+ ? `Loaded schema from ${schemas[0].schemaPath}`
115
+ : `Loaded ${count(schemas.length, 'schemas')}`,
116
+ )
117
+
118
+ spinner.start('Generating schema types…')
119
+ fileHandle = await open(outputPath, 'w')
120
+ await fileHandle.write(GENERATED_FILE_WARNING)
121
+ const schemaResult = await receiver.event.generatedSchemaDeclarations()
122
+ schemaStats = schemaResult.schemaStats
123
+ await fileHandle.write(schemaResult.code)
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)
116
147
  }
117
148
 
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
149
+ if (queryResult.type === 'declaration') {
150
+ await fileHandle.write(queryResult.code)
124
151
  }
152
+ }
125
153
 
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
154
+ const result = await receiver.event.generationComplete()
155
+ queryStats = result.queryStats
156
+ await fileHandle.write(result.augmentedQueryResultDeclarations.code)
157
+ await fileHandle.close()
158
+ fileHandle = null
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}`)
133
188
  }
189
+ }
134
190
 
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
- }
191
+ // Gather final stats and report success
192
+ const outputStat = await stat(outputPath)
193
+
194
+ trace.log({
195
+ outputSize: outputStat.size,
196
+ ...schemaStats,
197
+ ...queryStats,
198
+ configOverloadClientMethods: overloadClientMethods,
199
+ configAugmentGroqModule: augmentGroqModule,
156
200
  })
157
- worker.addListener('error', reject)
158
- })
159
201
 
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()
202
+ if (queryStats.errorCount > 0) {
203
+ spinner.warn(
204
+ `Encountered ${count(queryStats.errorCount, 'errors')} in ${count(queryStats.filesWithErrors, 'files')} while generating types to ${generates}`,
205
+ )
206
+ } else {
207
+ spinner.succeed(`Successfully generated types to ${generates}`)
185
208
  }
209
+ } catch (err) {
210
+ trace.error(err)
211
+ throw err
212
+ } finally {
213
+ await fileHandle?.close()
214
+ await receiver.dispose()
215
+ trace.complete()
186
216
  }
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
- )
210
217
  }
package/src/cli.ts CHANGED
File without changes
@@ -2,79 +2,75 @@ import {type CliCommandDefinition} from '../../types'
2
2
 
3
3
  const helpText = `
4
4
  Arguments
5
- [type] Type of Resource to add (currently only 'function' is supported)
5
+ <type> Type of Resource to add (currently only 'function' is supported)
6
6
 
7
- Examples
8
- # Add a Function Resource
7
+ Options
8
+ --name <name> Name of the Resource
9
+ --fn-type <type> Type of Function Resource to add (e.g. document-publish)
10
+ --fn-lang, --lang <ts|js> Language of the Function Resource
11
+ --js, --javascript Use JavaScript for the Function Resource
12
+
13
+ Examples:
14
+ # Add a Function Resource (TypeScript by default)
9
15
  sanity blueprints add function
16
+
17
+ # Add a Function Resource with a specific name
18
+ sanity blueprints add function --name my-function
19
+
20
+ # Add a Function Resource with a specific type
21
+ sanity blueprints add function --name my-function --fn-type document-publish
22
+
23
+ # Add a Function Resource in JavaScript
24
+ sanity blueprints add function --name my-function --fn-type document-publish --js
10
25
  `
11
26
 
12
- const addBlueprintsCommand: CliCommandDefinition = {
27
+ export interface BlueprintsAddFlags {
28
+ 'name'?: string
29
+ 'fn-type'?: string
30
+ 'fn-lang'?: string
31
+ 'language'?: string
32
+ 'lang'?: string
33
+ 'js'?: boolean
34
+ 'javascript'?: boolean
35
+ }
36
+
37
+ const defaultFlags: BlueprintsAddFlags = {
38
+ //
39
+ }
40
+
41
+ const addBlueprintsCommand: CliCommandDefinition<BlueprintsAddFlags> = {
13
42
  name: 'add',
14
43
  group: 'blueprints',
15
44
  helpText,
16
- signature: '<type>',
45
+ signature:
46
+ '<type> [--name <name>] [--fn-type <document-publish>] [--fn-lang <ts|js>] [--javascript]',
17
47
  description: 'Add a Resource to a Blueprint',
18
- hideFromHelp: true,
48
+
19
49
  async action(args, context) {
20
- const {output, prompt} = context
21
- const {print} = output
50
+ const {output} = context
51
+ const flags = {...defaultFlags, ...args.extOptions}
22
52
 
23
53
  const [resourceType] = args.argsWithoutOptions
24
54
 
25
55
  if (!resourceType) {
26
- print('Resource type is required. Available types: function')
27
- return
28
- }
29
-
30
- const {blueprint: blueprintAction, resources: resourcesAction} = await import(
31
- '@sanity/runtime-cli/actions/blueprints'
32
- )
33
-
34
- const existingBlueprint = blueprintAction.findBlueprintFile()
35
- if (!existingBlueprint) {
36
- print('No blueprint file found. Run `sanity blueprints init` first.')
56
+ output.error('Resource type is required. Available types: function')
37
57
  return
38
58
  }
39
59
 
40
- if (resourceType === 'function') {
41
- const functionName = await prompt.single({
42
- type: 'input',
43
- message: 'Enter function name:',
44
- validate: (input: string) => input.length > 0 || 'Function name is required',
45
- })
46
-
47
- const functionType = await prompt.single({
48
- type: 'list',
49
- message: 'Choose function type:',
50
- choices: [
51
- {value: 'document-publish', name: 'Document Publish'},
52
- {value: 'document-create', name: 'Document Create (Available soon)', disabled: true},
53
- {value: 'document-delete', name: 'Document Delete (Available soon)', disabled: true},
54
- ],
55
- })
56
-
57
- const {filePath, resourceAdded, resource} = resourcesAction.createFunctionResource({
58
- name: functionName,
59
- type: functionType,
60
- displayName: functionName,
61
- })
62
-
63
- print(`\nCreated function: ${filePath}`)
64
-
65
- if (resourceAdded) {
66
- // added to blueprint.json
67
- print('Function Resource added to Blueprint')
68
- } else {
69
- // print the resource JSON for manual addition
70
- print('\nAdd this Function Resource to your Blueprint:')
71
- print(JSON.stringify(resource, null, 2))
72
- }
73
-
74
- return
75
- }
60
+ const {blueprintAddCore} = await import('@sanity/runtime-cli/cores/blueprints')
61
+ const {success, error} = await blueprintAddCore({
62
+ bin: 'sanity',
63
+ log: (msg) => output.print(msg),
64
+ args: {type: resourceType},
65
+ flags: {
66
+ 'name': flags.name,
67
+ 'fn-type': flags['fn-type'],
68
+ 'language': flags['fn-lang'] ?? flags.language ?? flags.lang,
69
+ 'javascript': flags.js || flags.javascript,
70
+ },
71
+ })
76
72
 
77
- print(`Unsupported resource type: ${resourceType}. Available types: function`)
73
+ if (!success) throw new Error(error)
78
74
  },
79
75
  }
80
76
 
@@ -5,7 +5,6 @@ const blueprintsGroup: CliCommandGroupDefinition = {
5
5
  signature: '[COMMAND]',
6
6
  isGroupRoot: true,
7
7
  description: 'Deploy and manage Sanity Blueprints and Stacks (IaC)',
8
- hideFromHelp: true,
9
8
  }
10
9
 
11
10
  export default blueprintsGroup
@@ -2,30 +2,51 @@ import {type CliCommandDefinition} from '../../types'
2
2
 
3
3
  const helpText = `
4
4
  Options
5
- --edit Edit the configuration
5
+ --edit, -e Edit the configuration
6
+ --test, -t Test the configuration
7
+ --project-id <id> Project ID to use
6
8
 
7
- Examples
9
+ Examples:
8
10
  # View current configuration
9
11
  sanity blueprints config
10
12
 
11
13
  # Edit configuration
12
14
  sanity blueprints config --edit
15
+
16
+ # Test configuration
17
+ sanity blueprints config --test
18
+
19
+ # Edit and test configuration
20
+ sanity blueprints config -et
13
21
  `
14
22
 
15
- const defaultFlags = {
16
- edit: false,
23
+ export interface BlueprintsConfigFlags {
24
+ 'test-config'?: boolean
25
+ 'test'?: boolean
26
+ 't'?: boolean
27
+ 'edit'?: boolean
28
+ 'e'?: boolean
29
+ 'project-id'?: string
30
+ 'projectId'?: string
31
+ 'project'?: string
32
+ 'stack-id'?: string
33
+ 'stackId'?: string
34
+ 'stack'?: string
35
+ }
36
+
37
+ const defaultFlags: BlueprintsConfigFlags = {
38
+ //
17
39
  }
18
40
 
19
- const configBlueprintsCommand: CliCommandDefinition = {
41
+ const configBlueprintsCommand: CliCommandDefinition<BlueprintsConfigFlags> = {
20
42
  name: 'config',
21
43
  group: 'blueprints',
22
44
  helpText,
23
- signature: '[--edit]',
45
+ signature: '[--edit] [-e] [--test] [-t] [--project-id <id>]',
24
46
  description: 'View or edit local Blueprints configuration',
25
- hideFromHelp: true,
47
+
26
48
  async action(args, context) {
27
- const {apiClient, output, prompt} = context
28
- const {print} = output
49
+ const {apiClient, output} = context
29
50
  const flags = {...defaultFlags, ...args.extOptions}
30
51
 
31
52
  const client = apiClient({
@@ -33,79 +54,34 @@ const configBlueprintsCommand: CliCommandDefinition = {
33
54
  requireProject: false,
34
55
  })
35
56
  const {token} = client.config()
36
- const {
37
- blueprint: blueprintAction,
38
- projects: projectsAction,
39
- stacks: stacksAction,
40
- } = await import('@sanity/runtime-cli/actions/blueprints')
41
-
42
- const config = blueprintAction.readConfigFile()
43
- if (!config) {
44
- print('No configuration found. Run `sanity blueprints init` first.')
45
- return
46
- }
47
57
 
48
- print('\nCurrent configuration:')
49
- print(JSON.stringify(config, null, 2))
50
-
51
- if (!flags.edit) {
52
- return
53
- }
58
+ if (!token) throw new Error('No API token found. Please run `sanity login`.')
54
59
 
55
- if (!token) {
56
- print('No API token found. Please run `sanity login` first.')
57
- return
58
- }
60
+ const {blueprintConfigCore} = await import('@sanity/runtime-cli/cores/blueprints')
61
+ const {getBlueprintAndStack} = await import('@sanity/runtime-cli/actions/blueprints')
62
+ const {display} = await import('@sanity/runtime-cli/utils')
59
63
 
60
- const {ok, projects, error} = await projectsAction.listProjects({token})
61
- if (!ok) {
62
- print(error)
63
- return
64
- }
64
+ const {localBlueprint, issues} = await getBlueprintAndStack({token})
65
65
 
66
- if (!projects || projects.length === 0) {
67
- print('No Projects found. Please create a Project in Sanity.io first.')
68
- return
66
+ if (issues) {
67
+ // print issues and continue
68
+ output.print(display.errors.presentBlueprintIssues(issues))
69
69
  }
70
70
 
71
- const projectChoices = projects.map(({displayName, id}) => ({
72
- value: id,
73
- name: `${displayName} <${id}>`,
74
- }))
75
-
76
- const projectId = await prompt.single({
77
- type: 'list',
78
- message: 'Select your Sanity Project:',
79
- choices: projectChoices,
80
- default: config.projectId,
71
+ const {success, error} = await blueprintConfigCore({
72
+ bin: 'sanity',
73
+ log: (message) => output.print(message),
74
+ blueprint: localBlueprint,
75
+ token,
76
+ flags: {
77
+ 'project-id': flags['project-id'] ?? flags.projectId ?? flags.project,
78
+ 'stack-id': flags['stack-id'] ?? flags.stackId ?? flags.stack,
79
+ 'test-config': flags['test-config'] ?? flags.test ?? flags.t,
80
+ 'edit': flags.edit ?? flags.e,
81
+ },
81
82
  })
82
83
 
83
- const auth = {token, projectId}
84
-
85
- // get stacks for selected project
86
- const {ok: stacksOk, stacks, error: stacksError} = await stacksAction.listStacks(auth)
87
- if (!stacksOk) {
88
- print(stacksError)
89
- return
90
- }
91
-
92
- let stackId
93
- if (stacks && stacks.length > 0) {
94
- const stackChoices = stacks.map(({name, id}) => ({
95
- value: id,
96
- name: `${name} <${id}>`,
97
- }))
98
-
99
- stackId = await prompt.single({
100
- type: 'list',
101
- message: 'Select a Stack:',
102
- choices: stackChoices,
103
- default: config.stackId,
104
- })
105
- }
106
-
107
- blueprintAction.writeConfigFile({projectId, stackId})
108
- print('\nConfiguration updated successfully.')
84
+ if (!success) throw new Error(error)
109
85
  },
110
86
  }
111
87