sanity 3.26.2-canary.52 → 3.26.2-canary.69

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 (112) hide show
  1. package/lib/_chunks/{_internal-2CJ5wSKF.js → _internal-6Pl2wJGj.js} +422 -201
  2. package/lib/_chunks/_internal-6Pl2wJGj.js.map +1 -0
  3. package/lib/_chunks/{_internal-79flWvL8.js → _internal-yQwMw2ht.js} +419 -203
  4. package/lib/_chunks/_internal-yQwMw2ht.js.map +1 -0
  5. package/lib/_chunks/{_internalBrowser-QAFz3SKp.js → _internalBrowser-HWvRXvlU.js} +22 -22
  6. package/lib/_chunks/_internalBrowser-HWvRXvlU.js.map +1 -0
  7. package/lib/_chunks/{_internalBrowser-Y0qKZ-GA.js → _internalBrowser-TWIhitgI.js} +22 -22
  8. package/lib/_chunks/_internalBrowser-TWIhitgI.js.map +1 -0
  9. package/lib/_chunks/{deployApiAction-SHteit1G.js → deployApiAction-TZcCtXan.js} +2 -2
  10. package/lib/_chunks/{deployApiAction-SHteit1G.js.map → deployApiAction-TZcCtXan.js.map} +1 -1
  11. package/lib/_chunks/{deployApiAction-bRyJpGOS.js → deployApiAction-rVL67VYW.js} +2 -2
  12. package/lib/_chunks/{deployApiAction-bRyJpGOS.js.map → deployApiAction-rVL67VYW.js.map} +1 -1
  13. package/lib/_chunks/{getStudioConfig-JSkc4GE0.js → getStudioWorkspaces-HX9o9-hl.js} +20 -4
  14. package/lib/_chunks/getStudioWorkspaces-HX9o9-hl.js.map +1 -0
  15. package/lib/_chunks/{index-EO9iRnDS.js → index-2kSxso3r.js} +2 -2
  16. package/lib/_chunks/{index-EO9iRnDS.js.map → index-2kSxso3r.js.map} +1 -1
  17. package/lib/_chunks/{index-FQCCBbuC.js → index-751ZLh3z.js} +6 -6
  18. package/lib/_chunks/{index-FQCCBbuC.js.map → index-751ZLh3z.js.map} +1 -1
  19. package/lib/_chunks/{index-VNSHvpZr.js → index-HcF369ru.js} +2 -2
  20. package/lib/_chunks/{index-VNSHvpZr.js.map → index-HcF369ru.js.map} +1 -1
  21. package/lib/_chunks/{index-Xs2xnLUV.js → index-MAAxgUnl.js} +6 -6
  22. package/lib/_chunks/{index-Xs2xnLUV.js.map → index-MAAxgUnl.js.map} +1 -1
  23. package/lib/_chunks/{index-AaK2CidU.js → index-NweJPfGb.js} +2 -2
  24. package/lib/_chunks/{index-AaK2CidU.js.map → index-NweJPfGb.js.map} +1 -1
  25. package/lib/_chunks/{index-R7R6AyHF.js → index-zobOqko7.js} +2 -2
  26. package/lib/_chunks/{index-R7R6AyHF.js.map → index-zobOqko7.js.map} +1 -1
  27. package/lib/_chunks/{listApisAction-6lGkFZrU.js → listApisAction-IvKV4iAd.js} +2 -2
  28. package/lib/_chunks/{listApisAction-6lGkFZrU.js.map → listApisAction-IvKV4iAd.js.map} +1 -1
  29. package/lib/_chunks/{listApisAction-CqCkBz-F.js → listApisAction-oca2uykY.js} +2 -2
  30. package/lib/_chunks/{listApisAction-CqCkBz-F.js.map → listApisAction-oca2uykY.js.map} +1 -1
  31. package/lib/_chunks/pane-9HEeITpx.js +5 -0
  32. package/lib/_chunks/pane-9HEeITpx.js.map +1 -0
  33. package/lib/_chunks/pane-QyVrOLqS.js +2 -0
  34. package/lib/_chunks/pane-QyVrOLqS.js.map +1 -0
  35. package/lib/_chunks/pane-TXXUUvdc.js +5 -0
  36. package/lib/_chunks/pane-TXXUUvdc.js.map +1 -0
  37. package/lib/_chunks/pane-cQxQtBcL.js +2 -0
  38. package/lib/_chunks/pane-cQxQtBcL.js.map +1 -0
  39. package/lib/_chunks/{structure-qJLnDJXq.js → structure-iqIDIH7-.js} +3 -3
  40. package/lib/_chunks/structure-iqIDIH7-.js.map +1 -0
  41. package/lib/_chunks/{structure-588eAwLd.js → structure-o_wMXC_G.js} +3 -3
  42. package/lib/_chunks/structure-o_wMXC_G.js.map +1 -0
  43. package/lib/_chunks/validateAction-4Jl_y_iB.js +154 -0
  44. package/lib/_chunks/validateAction-4Jl_y_iB.js.map +1 -0
  45. package/lib/_chunks/{validateAction-GUvMkXiN.js → validateAction-6q8Sqwaz.js} +25 -73
  46. package/lib/_chunks/validateAction-6q8Sqwaz.js.map +1 -0
  47. package/lib/_chunks/validateAction-Bz67ApRP.js +143 -0
  48. package/lib/_chunks/validateAction-Bz67ApRP.js.map +1 -0
  49. package/lib/_chunks/{validateAction-NIrqtyYJ.js → validateAction-shi462sq.js} +27 -75
  50. package/lib/_chunks/validateAction-shi462sq.js.map +1 -0
  51. package/lib/_internal/cli/threads/getGraphQLAPIs.js +2 -2
  52. package/lib/_internal/cli/threads/getGraphQLAPIs.js.map +1 -1
  53. package/lib/_internal/cli/threads/validateDocuments.js +5 -3
  54. package/lib/_internal/cli/threads/validateDocuments.js.map +1 -1
  55. package/lib/_internal/cli/threads/validateSchema.js +55 -0
  56. package/lib/_internal/cli/threads/validateSchema.js.map +1 -0
  57. package/lib/_internal.esm.js +1 -1
  58. package/lib/_internal.js +1 -1
  59. package/lib/_internalBrowser.esm.js +1 -1
  60. package/lib/_internalBrowser.js +1 -1
  61. package/lib/desk.esm.js +1 -1
  62. package/lib/desk.js +1 -1
  63. package/lib/exports/index.d.ts +25 -0
  64. package/lib/index.cjs.mjs +1 -0
  65. package/lib/index.esm.js +2 -2
  66. package/lib/index.js +2 -1
  67. package/lib/index.js.map +1 -1
  68. package/lib/structure.esm.js +1 -1
  69. package/lib/structure.js +1 -1
  70. package/package.json +15 -15
  71. package/src/_internal/cli/actions/schema/formatSchemaValidation.ts +96 -0
  72. package/src/_internal/cli/actions/schema/validateAction.ts +120 -0
  73. package/src/_internal/cli/actions/validation/reporters/prettyReporter/formatDocumentValidation.ts +17 -95
  74. package/src/_internal/cli/commands/index.ts +2 -0
  75. package/src/_internal/cli/commands/migration/createMigrationCommand.ts +18 -10
  76. package/src/_internal/cli/commands/migration/listMigrationsCommand.ts +34 -31
  77. package/src/_internal/cli/commands/migration/prettyMutationFormatter.ts +214 -0
  78. package/src/_internal/cli/commands/migration/runMigrationCommand.ts +102 -58
  79. package/src/_internal/cli/commands/migration/templates/minimalAdvanced.ts +1 -1
  80. package/src/_internal/cli/commands/migration/templates/minimalSimple.ts +1 -1
  81. package/src/_internal/cli/commands/migration/templates/renameField.ts +3 -3
  82. package/src/_internal/cli/commands/migration/templates/renameType.ts +1 -1
  83. package/src/_internal/cli/commands/migration/templates/stringToPTE.ts +1 -1
  84. package/src/_internal/cli/commands/migration/utils/mutationFormatter.ts +1 -1
  85. package/src/_internal/cli/commands/schema/validateSchemaCommand.ts +35 -0
  86. package/src/_internal/cli/threads/getGraphQLAPIs.ts +2 -2
  87. package/src/_internal/cli/threads/validateDocuments.ts +6 -3
  88. package/src/_internal/cli/threads/validateSchema.ts +73 -0
  89. package/src/_internal/cli/util/{getStudioConfig.ts → getStudioWorkspaces.ts} +30 -8
  90. package/src/_internal/cli/util/tree.ts +110 -0
  91. package/src/core/config/index.ts +1 -0
  92. package/src/core/config/prepareConfig.ts +2 -5
  93. package/src/core/config/resolveSchemaTypes.ts +29 -0
  94. package/src/core/studio/screens/schemaErrors/SchemaProblemGroups.tsx +4 -2
  95. package/src/structure/comments/src/components/pte/blocks/MentionInlineBlock.tsx +13 -6
  96. package/lib/_chunks/_internal-2CJ5wSKF.js.map +0 -1
  97. package/lib/_chunks/_internal-79flWvL8.js.map +0 -1
  98. package/lib/_chunks/_internalBrowser-QAFz3SKp.js.map +0 -1
  99. package/lib/_chunks/_internalBrowser-Y0qKZ-GA.js.map +0 -1
  100. package/lib/_chunks/getStudioConfig-JSkc4GE0.js.map +0 -1
  101. package/lib/_chunks/pane-QmJb9ZTN.js +0 -2
  102. package/lib/_chunks/pane-QmJb9ZTN.js.map +0 -1
  103. package/lib/_chunks/pane-SK7FWNTJ.js +0 -2
  104. package/lib/_chunks/pane-SK7FWNTJ.js.map +0 -1
  105. package/lib/_chunks/pane-y4hpcKe1.js +0 -5
  106. package/lib/_chunks/pane-y4hpcKe1.js.map +0 -1
  107. package/lib/_chunks/pane-yQXBQyyI.js +0 -5
  108. package/lib/_chunks/pane-yQXBQyyI.js.map +0 -1
  109. package/lib/_chunks/structure-588eAwLd.js.map +0 -1
  110. package/lib/_chunks/structure-qJLnDJXq.js.map +0 -1
  111. package/lib/_chunks/validateAction-GUvMkXiN.js.map +0 -1
  112. package/lib/_chunks/validateAction-NIrqtyYJ.js.map +0 -1
@@ -1,4 +1,4 @@
1
- export { ComponentBuilder, ComponentViewBuilder, ConfirmDeleteDialogContainer as ConfirmDeleteDialog, DEFAULT_INTENT_HANDLER, DocumentBuilder, DocumentInspectorHeader, DocumentListBuilder, DocumentListItemBuilder, DocumentListPane, DocumentPane, DocumentPaneProvider, DocumentTypeListBuilder, FormViewBuilder, GenericListBuilder, GenericViewBuilder, HELP_URL, InitialValueTemplateItemBuilder, ListBuilder, ListItemBuilder, MenuItemBuilder, MenuItemGroupBuilder, PaneLayout, PaneRouterContext, SerializeError, StructureToolProvider, component, createStructureBuilder, defaultInitialValueTemplateItems, defaultIntentChecker, documentFromEditor, documentFromEditorWithInitialValue, form, getOrderingMenuItem, getOrderingMenuItemsForSchemaType, getTypeNamesFromFilter, isDocumentListItem, maybeSerializeInitialValueTemplateItem, maybeSerializeMenuItem, maybeSerializeMenuItemGroup, maybeSerializeView, menuItemsFromInitialValueTemplateItems, shallowIntentChecker, structureLocaleNamespace, structureTool, useDocumentPane, useDocumentTitle, usePaneRouter, useStructureTool } from './_chunks/structure-qJLnDJXq.js';
1
+ export { ComponentBuilder, ComponentViewBuilder, ConfirmDeleteDialogContainer as ConfirmDeleteDialog, DEFAULT_INTENT_HANDLER, DocumentBuilder, DocumentInspectorHeader, DocumentListBuilder, DocumentListItemBuilder, DocumentListPane, DocumentPane, DocumentPaneProvider, DocumentTypeListBuilder, FormViewBuilder, GenericListBuilder, GenericViewBuilder, HELP_URL, InitialValueTemplateItemBuilder, ListBuilder, ListItemBuilder, MenuItemBuilder, MenuItemGroupBuilder, PaneLayout, PaneRouterContext, SerializeError, StructureToolProvider, component, createStructureBuilder, defaultInitialValueTemplateItems, defaultIntentChecker, documentFromEditor, documentFromEditorWithInitialValue, form, getOrderingMenuItem, getOrderingMenuItemsForSchemaType, getTypeNamesFromFilter, isDocumentListItem, maybeSerializeInitialValueTemplateItem, maybeSerializeMenuItem, maybeSerializeMenuItemGroup, maybeSerializeView, menuItemsFromInitialValueTemplateItems, shallowIntentChecker, structureLocaleNamespace, structureTool, useDocumentPane, useDocumentTitle, usePaneRouter, useStructureTool } from './_chunks/structure-iqIDIH7-.js';
2
2
  import 'react/jsx-runtime';
3
3
  import 'react';
4
4
  import '@sanity/ui';
package/lib/structure.js CHANGED
@@ -3,7 +3,7 @@
3
3
  Object.defineProperty(exports, '__esModule', {
4
4
  value: true
5
5
  });
6
- var structure = require('./_chunks/structure-588eAwLd.js');
6
+ var structure = require('./_chunks/structure-o_wMXC_G.js');
7
7
  require('react/jsx-runtime');
8
8
  require('react');
9
9
  require('@sanity/ui');
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "sanity",
3
- "version": "3.26.2-canary.52+be0d838591",
3
+ "version": "3.26.2-canary.69+747ad532f2",
4
4
  "description": "Sanity is a real-time content infrastructure with a scalable, hosted backend featuring a Graph Oriented Query Language (GROQ), asset pipelines and fast edge caches",
5
5
  "keywords": [
6
6
  "sanity",
@@ -199,28 +199,28 @@
199
199
  "@rexxars/react-json-inspector": "^8.0.1",
200
200
  "@sanity/asset-utils": "^1.2.5",
201
201
  "@sanity/bifur-client": "^0.3.1",
202
- "@sanity/block-tools": "3.26.2-canary.52+be0d838591",
203
- "@sanity/cli": "3.26.2-canary.52+be0d838591",
204
- "@sanity/client": "^6.11.1",
202
+ "@sanity/block-tools": "3.26.2-canary.69+747ad532f2",
203
+ "@sanity/cli": "3.26.2-canary.69+747ad532f2",
204
+ "@sanity/client": "^6.12.3",
205
205
  "@sanity/color": "^3.0.0",
206
- "@sanity/diff": "3.26.2-canary.52+be0d838591",
206
+ "@sanity/diff": "3.26.2-canary.69+747ad532f2",
207
207
  "@sanity/diff-match-patch": "^3.1.1",
208
208
  "@sanity/eventsource": "^5.0.0",
209
- "@sanity/export": "3.26.2-canary.52+be0d838591",
209
+ "@sanity/export": "3.26.2-canary.69+747ad532f2",
210
210
  "@sanity/generate-help-url": "^3.0.0",
211
211
  "@sanity/icons": "^2.8.0",
212
212
  "@sanity/image-url": "^1.0.2",
213
- "@sanity/import": "3.26.2-canary.52+be0d838591",
213
+ "@sanity/import": "3.26.2-canary.69+747ad532f2",
214
214
  "@sanity/logos": "^2.1.4",
215
- "@sanity/migrate": "3.26.2-canary.52+be0d838591",
216
- "@sanity/mutator": "3.26.2-canary.52+be0d838591",
217
- "@sanity/portable-text-editor": "3.26.2-canary.52+be0d838591",
218
- "@sanity/presentation": "1.7.1",
219
- "@sanity/schema": "3.26.2-canary.52+be0d838591",
215
+ "@sanity/migrate": "3.26.2-canary.69+747ad532f2",
216
+ "@sanity/mutator": "3.26.2-canary.69+747ad532f2",
217
+ "@sanity/portable-text-editor": "3.26.2-canary.69+747ad532f2",
218
+ "@sanity/presentation": "1.7.2",
219
+ "@sanity/schema": "3.26.2-canary.69+747ad532f2",
220
220
  "@sanity/telemetry": "^0.7.6",
221
- "@sanity/types": "3.26.2-canary.52+be0d838591",
221
+ "@sanity/types": "3.26.2-canary.69+747ad532f2",
222
222
  "@sanity/ui": "^2.0.0",
223
- "@sanity/util": "3.26.2-canary.52+be0d838591",
223
+ "@sanity/util": "3.26.2-canary.69+747ad532f2",
224
224
  "@sanity/uuid": "^3.0.1",
225
225
  "@tanstack/react-virtual": "3.0.0-beta.54",
226
226
  "@types/is-hotkey": "^0.1.7",
@@ -334,5 +334,5 @@
334
334
  "engines": {
335
335
  "node": ">=18"
336
336
  },
337
- "gitHead": "be0d83859183df1d9d7413775c453b32f0ec6dd8"
337
+ "gitHead": "747ad532f2f9e2d3dd4e859262bd2a8fc859296e"
338
338
  }
@@ -0,0 +1,96 @@
1
+ import {isatty} from 'tty'
2
+ import chalk from 'chalk'
3
+ import {SchemaValidationProblemGroup, SchemaValidationProblemPath} from '@sanity/types'
4
+ import logSymbols from 'log-symbols'
5
+
6
+ const isTty = isatty(1)
7
+
8
+ const headers = {
9
+ error: isTty ? chalk.bold(chalk.bgRed(chalk.black(' ERROR '))) : chalk.red('[ERROR]'),
10
+ warning: isTty ? chalk.bold(chalk.bgYellow(chalk.black(' WARN '))) : chalk.yellow('[WARN]'),
11
+ }
12
+
13
+ const severityValues = {error: 0, warning: 1}
14
+
15
+ function formatPath(pathSegments: SchemaValidationProblemPath) {
16
+ const format = (
17
+ [curr, ...next]: SchemaValidationProblemPath,
18
+ mode: 'object' | 'array' = 'object',
19
+ ): string => {
20
+ if (!curr) return ''
21
+ if (curr.kind === 'property') return format(next, curr.name === 'of' ? 'array' : 'object')
22
+
23
+ const name = curr.name ? curr.name : `<anonymous_${curr.type}>`
24
+ return `${mode === 'array' ? `[${name}]` : `.${name}`}${format(next)}`
25
+ }
26
+
27
+ return format(pathSegments.slice(1)).substring(1) // removes the top-level type and leading `.`
28
+ }
29
+
30
+ export function getAggregatedSeverity(
31
+ groupOrGroups: SchemaValidationProblemGroup | SchemaValidationProblemGroup[],
32
+ ): 'error' | 'warning' {
33
+ const groups = Array.isArray(groupOrGroups) ? groupOrGroups : [groupOrGroups]
34
+ return groups
35
+ .flatMap((group) => group.problems.map((problem) => problem.severity))
36
+ .find((severity) => severity === 'error')
37
+ ? 'error'
38
+ : 'warning'
39
+ }
40
+
41
+ export function formatSchemaValidation(validation: SchemaValidationProblemGroup[]): string {
42
+ let unnamedTopLevelTypeCount = 0
43
+ const validationByType = Object.entries(
44
+ validation.reduce<Record<string, SchemaValidationProblemGroup[]>>((acc, next) => {
45
+ const [firstSegment] = next.path
46
+ if (!firstSegment) return acc
47
+ if (firstSegment.kind !== 'type') return acc
48
+
49
+ const topLevelType =
50
+ firstSegment.name || `<unnamed_${firstSegment.type}_type_${unnamedTopLevelTypeCount++}>`
51
+ const problems = acc[topLevelType] ?? []
52
+
53
+ problems.push(next)
54
+
55
+ acc[topLevelType] = problems
56
+ return acc
57
+ }, {}),
58
+ )
59
+
60
+ const formatted = validationByType
61
+ .sort((a, b) => {
62
+ const [aType, aGroups] = a
63
+ const [bType, bGroups] = b
64
+ const aValue = severityValues[getAggregatedSeverity(aGroups)]
65
+ const bValue = severityValues[getAggregatedSeverity(bGroups)]
66
+ if (aValue === bValue) return aType.localeCompare(bType, 'en-US')
67
+ return aValue - bValue
68
+ })
69
+ .map(([topLevelType, groups]) => {
70
+ const formattedTopLevelType = isTty
71
+ ? chalk.bgWhite(chalk.black(` ${topLevelType} `))
72
+ : `[${topLevelType}]`
73
+
74
+ const header = `${headers[getAggregatedSeverity(groups)]} ${formattedTopLevelType}`
75
+ const body = groups
76
+ .sort(
77
+ (a, b) =>
78
+ severityValues[getAggregatedSeverity(a)] - severityValues[getAggregatedSeverity(b)],
79
+ )
80
+ .map((group) => {
81
+ const formattedPath = ` ${chalk.bold(formatPath(group.path) || '(root)')}`
82
+ const formattedMessages = group.problems
83
+ .sort((a, b) => severityValues[a.severity] - severityValues[b.severity])
84
+ .map(({severity, message}) => ` ${logSymbols[severity]} ${message}`)
85
+ .join('\n')
86
+
87
+ return `${formattedPath}\n${formattedMessages}`
88
+ })
89
+ .join('\n')
90
+
91
+ return `${header}\n${body}`
92
+ })
93
+ .join('\n\n')
94
+
95
+ return formatted
96
+ }
@@ -0,0 +1,120 @@
1
+ import path from 'path'
2
+ import {Worker} from 'worker_threads'
3
+ import {type CliCommandArguments, type CliCommandContext} from '@sanity/cli'
4
+ import readPkgUp from 'read-pkg-up'
5
+ import logSymbols from 'log-symbols'
6
+ import {
7
+ type ValidateSchemaWorkerData,
8
+ type ValidateSchemaWorkerResult,
9
+ } from '../../threads/validateSchema'
10
+ import {formatSchemaValidation, getAggregatedSeverity} from './formatSchemaValidation'
11
+
12
+ interface ValidateFlags {
13
+ workspace?: string
14
+ format?: string
15
+ level?: 'error' | 'warning'
16
+ }
17
+
18
+ export type SchemaValidationFormatter = (result: ValidateSchemaWorkerResult) => string
19
+
20
+ export default async function validateAction(
21
+ args: CliCommandArguments<ValidateFlags>,
22
+ {workDir, output}: CliCommandContext,
23
+ ): Promise<void> {
24
+ const flags = args.extOptions
25
+
26
+ const rootPkgPath = readPkgUp.sync({cwd: __dirname})?.path
27
+ if (!rootPkgPath) {
28
+ throw new Error('Could not find root directory for `sanity` package')
29
+ }
30
+
31
+ const workerPath = path.join(
32
+ path.dirname(rootPkgPath),
33
+ 'lib',
34
+ '_internal',
35
+ 'cli',
36
+ 'threads',
37
+ 'validateSchema.js',
38
+ )
39
+
40
+ const level = flags.level || 'warning'
41
+
42
+ if (level !== 'error' && level !== 'warning') {
43
+ throw new Error(`Invalid level. Available levels are 'error' and 'warning'.`)
44
+ }
45
+
46
+ const format = flags.format || 'pretty'
47
+
48
+ if (!['pretty', 'ndjson', 'json'].includes(format)) {
49
+ throw new Error(
50
+ `Did not recognize format '${flags.format}'. Available formats are 'pretty', 'ndjson', and 'json'.`,
51
+ )
52
+ }
53
+
54
+ let spinner
55
+
56
+ if (format === 'pretty') {
57
+ spinner = output
58
+ .spinner(
59
+ flags.workspace
60
+ ? `Validating schema from workspace '${flags.workspace}'…`
61
+ : 'Validating schema…',
62
+ )
63
+ .start()
64
+ }
65
+
66
+ const worker = new Worker(workerPath, {
67
+ workerData: {
68
+ workDir,
69
+ level,
70
+ workspace: flags.workspace,
71
+ } satisfies ValidateSchemaWorkerData,
72
+ // eslint-disable-next-line no-process-env
73
+ env: process.env,
74
+ })
75
+
76
+ const {validation} = await new Promise<ValidateSchemaWorkerResult>((resolve, reject) => {
77
+ worker.addListener('message', resolve)
78
+ worker.addListener('error', reject)
79
+ })
80
+
81
+ const problems = validation.flatMap((group) => group.problems)
82
+ const errorCount = problems.filter((problem) => problem.severity === 'error').length
83
+ const warningCount = problems.filter((problem) => problem.severity === 'warning').length
84
+
85
+ const overallSeverity = getAggregatedSeverity(validation)
86
+
87
+ switch (format) {
88
+ case 'ndjson': {
89
+ for (const group of validation) {
90
+ output.print(JSON.stringify(group))
91
+ }
92
+ break
93
+ }
94
+ case 'json': {
95
+ output.print(JSON.stringify(validation))
96
+ break
97
+ }
98
+ default: {
99
+ spinner?.succeed('Validated schema')
100
+ output.print(`\nValidation results:`)
101
+ output.print(
102
+ `${logSymbols.error} Errors: ${errorCount.toLocaleString('en-US')} error${
103
+ errorCount === 1 ? '' : 's'
104
+ }`,
105
+ )
106
+ if (level !== 'error') {
107
+ output.print(
108
+ `${logSymbols.warning} Warnings: ${warningCount.toLocaleString('en-US')} warning${
109
+ warningCount === 1 ? '' : 's'
110
+ }`,
111
+ )
112
+ }
113
+ output.print()
114
+
115
+ output.print(formatSchemaValidation(validation))
116
+ }
117
+ }
118
+
119
+ process.exitCode = overallSeverity === 'error' ? 1 : 0
120
+ }
@@ -1,19 +1,20 @@
1
1
  import chalk from 'chalk'
2
- import {ValidationMarker} from '@sanity/types'
2
+ import {ValidationMarker, Path} from '@sanity/types'
3
3
  import logSymbols from 'log-symbols'
4
+ import {Tree, convertToTree, formatTree, maxKeyLength} from '../../../../util/tree'
4
5
  import {DocumentValidationResult, Level, isTty, levelValues} from './util'
5
- import {pathToString} from 'sanity'
6
6
 
7
7
  export interface FormatDocumentValidationOptions extends DocumentValidationResult {
8
8
  studioHost: string | null
9
9
  basePath: string
10
10
  }
11
11
 
12
- interface ValidationTree {
13
- markers?: Pick<ValidationMarker, 'level' | 'message'>[]
14
- children?: Record<string, ValidationTree>
12
+ interface Marker extends Pick<ValidationMarker, 'level' | 'message'> {
13
+ path: Path
15
14
  }
16
15
 
16
+ type ValidationTree = Tree<Marker>
17
+
17
18
  const levelHeaders = {
18
19
  error: isTty ? chalk.bold(chalk.bgRed(chalk.black(' ERROR '))) : chalk.red('[ERROR]'),
19
20
  warning: isTty ? chalk.bold(chalk.bgYellow(chalk.black(' WARN '))) : chalk.yellow('[WARN]'),
@@ -26,73 +27,19 @@ const levelHeaders = {
26
27
  const link = (text: string, url: string) =>
27
28
  isTty ? `\u001b]8;;${url}\u0007${text}\u001b]8;;\u0007` : chalk.underline(text)
28
29
 
29
- /**
30
- * Recursively calculates the max length of all the keys in the given validation
31
- * tree respecting extra length due to indentation depth. Used to calculate the
32
- * padding for the rest of the tree.
33
- */
34
- const maxKeyLength = (children: Record<string, ValidationTree> = {}, depth = 0): number => {
35
- return Object.entries(children)
36
- .map(([key, child]) =>
37
- Math.max(key.length + depth * 2, maxKeyLength(child.children, depth + 1)),
38
- )
39
- .reduce((max, next) => (next > max ? next : max), 0)
40
- }
41
-
42
30
  /**
43
31
  * For sorting markers
44
32
  */
45
33
  const compareLevels = <T extends {level: Level; message: string}>(a: T, b: T) =>
46
34
  levelValues[a.level] - levelValues[b.level]
47
35
 
48
- /**
49
- * Recursively formats a given tree into a printed user-friendly tree structure
50
- */
51
- const formatTree = (
52
- node: Record<string, ValidationTree> = {},
53
- paddingLength: number,
54
- indent = '',
55
- ): string => {
56
- const entries = Object.entries(node)
57
-
58
- return entries
59
- .map(([key, child], index) => {
60
- const isLast = index === entries.length - 1
61
- const nextIndent = `${indent}${isLast ? ' ' : '│ '}`
62
- const nested = formatTree(child.children, paddingLength, nextIndent)
63
-
64
- if (!child.markers?.length) {
65
- const current = `${indent}${isLast ? '└' : '├'}─ ${key}`
66
- return [current, nested].filter(Boolean).join('\n')
67
- }
68
-
69
- const [first, ...rest] = child.markers.slice().sort(compareLevels)
70
- const firstPadding = '.'.repeat(paddingLength - indent.length - key.length)
71
- const elbow = isLast ? '└' : '├'
72
- const firstBullet = logSymbols[first.level]
73
- const subsequentPadding = ' '.repeat(paddingLength - indent.length + 2)
74
-
75
- const firstMessage = `${indent}${elbow}─ ${key} ${firstPadding} ${firstBullet} ${first.message}`
76
- const subsequentMessages = rest
77
- .map(
78
- (marker) =>
79
- `${nextIndent}${subsequentPadding} ${logSymbols[marker.level]} ${marker.message}`,
80
- )
81
- .join('\n')
82
-
83
- const current = [firstMessage, subsequentMessages].filter(Boolean).join('\n')
84
- return [current, nested].filter(Boolean).join('\n')
85
- })
86
- .join('\n')
87
- }
88
-
89
36
  /**
90
37
  * Formats the markers at the root of the validation tree
91
38
  */
92
39
  const formatRootErrors = (root: ValidationTree, hasChildren: boolean, paddingLength: number) => {
93
- if (!root.markers) return ''
40
+ if (!root.nodes) return ''
94
41
 
95
- const [first, ...rest] = root.markers.slice().sort(compareLevels)
42
+ const [first, ...rest] = root.nodes.slice().sort(compareLevels)
96
43
  if (!first) return ''
97
44
 
98
45
  const firstElbow = hasChildren ? '│ ' : '└─'
@@ -112,38 +59,6 @@ const formatRootErrors = (root: ValidationTree, hasChildren: boolean, paddingLen
112
59
  return [firstLine, restOfLines].filter(Boolean).join('\n')
113
60
  }
114
61
 
115
- /**
116
- * Converts a set of markers with paths into a tree of markers where the paths
117
- * are embedded in the tree
118
- */
119
- function convertToTree(markers: ValidationMarker[]): ValidationTree {
120
- const root: ValidationTree = {}
121
-
122
- // add the markers to the tree
123
- function addMarker(marker: ValidationMarker, node: ValidationTree = root) {
124
- // if we've traversed the whole path
125
- if (!marker.path.length) {
126
- if (!node.markers) node.markers = [] // ensure markers is defined
127
-
128
- // then add the marker to the front
129
- node.markers.push({level: marker.level, message: marker.message})
130
- return
131
- }
132
-
133
- const [current, ...rest] = marker.path
134
- const key = pathToString([current])
135
-
136
- // ensure the current node has children and the next node
137
- if (!node.children) node.children = {}
138
- if (!(key in node.children)) node.children[key] = {}
139
-
140
- addMarker({...marker, path: rest}, node.children[key])
141
- }
142
-
143
- for (const marker of markers) addMarker(marker)
144
- return root
145
- }
146
-
147
62
  /**
148
63
  * Formats document validation results into a user-friendly tree structure
149
64
  */
@@ -155,7 +70,7 @@ export function formatDocumentValidation({
155
70
  studioHost,
156
71
  markers,
157
72
  }: FormatDocumentValidationOptions): string {
158
- const tree = convertToTree(markers)
73
+ const tree = convertToTree<Marker>(markers)
159
74
  const editLink =
160
75
  studioHost &&
161
76
  `${studioHost}${basePath}/intent/edit/id=${encodeURIComponent(
@@ -171,7 +86,14 @@ export function formatDocumentValidation({
171
86
  }`
172
87
 
173
88
  const paddingLength = Math.max(maxKeyLength(tree.children) + 2, 30)
174
- const childErrors = formatTree(tree.children, paddingLength)
89
+
90
+ const childErrors = formatTree<Marker>({
91
+ node: tree.children,
92
+ paddingLength,
93
+ getNodes: ({nodes}) => (nodes ?? []).slice().sort(compareLevels),
94
+ getMessage: (marker) => [logSymbols[marker.level], marker.message].join(' '),
95
+ })
96
+
175
97
  const rootErrors = formatRootErrors(tree, childErrors.length > 0, paddingLength)
176
98
 
177
99
  return [header, rootErrors, childErrors].filter(Boolean).join('\n')
@@ -21,6 +21,7 @@ import createDocumentsCommand from './documents/createDocumentsCommand'
21
21
  import validateDocumentsCommand from './documents/validateDocumentsCommand'
22
22
  import devCommand from './dev/devCommand'
23
23
  import startCommand from './start/startCommand'
24
+ import validateSchemaCommand from './schema/validateSchemaCommand'
24
25
  import previewCommand from './preview/previewCommand'
25
26
  import uninstallCommand from './uninstall/uninstallCommand'
26
27
  import hookGroup from './hook/hookGroup'
@@ -90,6 +91,7 @@ const commands: (CliCommandDefinition | CliCommandGroupDefinition)[] = [
90
91
  deleteGraphQLAPICommand,
91
92
  devCommand,
92
93
  startCommand,
94
+ validateSchemaCommand,
93
95
  previewCommand,
94
96
  uninstallCommand,
95
97
  execCommand,
@@ -13,6 +13,13 @@ import {renameField} from './templates/renameField'
13
13
 
14
14
  const helpText = `
15
15
  Create a new migration within your project
16
+
17
+ Examples:
18
+ # Create a new migration, you will be prompted to provide a type
19
+ sanity migration create
20
+
21
+ # Create a new migration, specifying the title
22
+ sanity migration create "Rename field from location to address"
16
23
  `
17
24
 
18
25
  // eslint-disable-next-line @typescript-eslint/no-empty-interface
@@ -32,21 +39,22 @@ const TEMPLATES = [
32
39
  const createMigrationCommand: CliCommandDefinition<CreateMigrationFlags> = {
33
40
  name: 'create',
34
41
  group: 'migration',
35
- signature: '[NAME]',
42
+ signature: '[TITLE]',
36
43
  helpText,
37
44
  description: 'Create a new content migration within your project',
38
45
  action: async (args, context) => {
39
46
  const {output, prompt, workDir, chalk} = context
40
47
 
41
- let name = ''
42
- while (!name.trim()) {
43
- name = await prompt.single({
48
+ let [title] = args.argsWithoutOptions
49
+
50
+ while (!title?.trim()) {
51
+ title = await prompt.single({
44
52
  type: 'input',
45
- suffix: ' (e.g. rename field from location to address)',
46
- message: 'Name of migration',
53
+ suffix: ' (e.g. "Rename field from location to address")',
54
+ message: 'Title of migration',
47
55
  })
48
- if (!name.trim()) {
49
- output.print(chalk.red('Name cannot be empty'))
56
+ if (!title.trim()) {
57
+ output.error(chalk.red('Name cannot be empty'))
50
58
  }
51
59
  }
52
60
  const types = await prompt.single({
@@ -65,7 +73,7 @@ const createMigrationCommand: CliCommandDefinition<CreateMigrationFlags> = {
65
73
  })),
66
74
  })
67
75
 
68
- const sluggedName = deburr(name.toLowerCase())
76
+ const sluggedName = deburr(title.toLowerCase())
69
77
  .replace(/\s+/g, '-')
70
78
  .replace(/[^a-z0-9-]/g, '')
71
79
 
@@ -84,7 +92,7 @@ const createMigrationCommand: CliCommandDefinition<CreateMigrationFlags> = {
84
92
  mkdirp.sync(destDir)
85
93
 
86
94
  const renderedTemplate = (templatesByName[template].template || minimalSimple)({
87
- migrationName: name,
95
+ migrationName: title,
88
96
  documentTypes: types
89
97
  .split(',')
90
98
  .map((t) => t.trim())
@@ -1,6 +1,6 @@
1
1
  import path from 'path'
2
2
  import {readdir} from 'node:fs/promises'
3
- import type {CliCommandDefinition} from '@sanity/cli'
3
+ import type {CliCommandContext, CliCommandDefinition} from '@sanity/cli'
4
4
  import {register} from 'esbuild-register/dist/node'
5
5
  import {Migration} from '@sanity/migrate'
6
6
  import {Table} from 'console-table-printer'
@@ -21,45 +21,48 @@ const createMigrationCommand: CliCommandDefinition<CreateFlags> = {
21
21
  helpText,
22
22
  description: 'List available migrations',
23
23
  action: async (args, context) => {
24
- const {output, workDir} = context
25
-
26
- if (!__DEV__) {
27
- register({
28
- target: `node${process.version.slice(1)}`,
29
- })
30
- }
31
-
32
- const directories = (
33
- await readdir(path.join(workDir, MIGRATIONS_DIRECTORY), {withFileTypes: true})
34
- ).filter((ent) => ent.isDirectory())
35
-
36
- const migrationModules = directories
37
- .map((ent) => {
38
- const candidates = resolveMigrationScript(workDir, ent.name)
39
- const found = candidates.find((candidate) => candidate.mod?.default)
40
- if (!found) {
41
- return null
42
- }
43
- return {
44
- dirname: ent.name,
45
- migration: found.mod.default as Migration,
46
- }
47
- })
48
- .filter(Boolean) as {dirname: string; migration: Migration}[]
24
+ const {workDir, output} = context
25
+ const migrations = await resolveMigrations(workDir)
49
26
  const table = new Table({
50
- title: `Found ${migrationModules.length} migrations in project`,
27
+ title: `Found ${migrations.length} migrations in project`,
51
28
  columns: [
52
29
  {name: 'id', title: 'ID', alignment: 'left'},
53
- {name: 'name', title: 'Name', alignment: 'left'},
30
+ {name: 'title', title: 'Title', alignment: 'left'},
54
31
  ],
55
32
  })
56
33
 
57
- migrationModules.forEach((definedMigration) => {
58
- table.addRow({id: definedMigration.dirname, name: definedMigration.migration.name})
34
+ migrations.forEach((definedMigration) => {
35
+ table.addRow({id: definedMigration.dirname, title: definedMigration.migration.title})
59
36
  })
60
37
  table.printTable()
61
- output.print(`\nRun "sanity migration run <MIGRATION ID>" to run a migration`)
38
+ output.print('\nRun `sanity migration run <ID>` to run a migration')
62
39
  },
63
40
  }
64
41
 
42
+ export async function resolveMigrations(workDir: string) {
43
+ if (!__DEV__) {
44
+ register({
45
+ target: `node${process.version.slice(1)}`,
46
+ })
47
+ }
48
+
49
+ const directories = (
50
+ await readdir(path.join(workDir, MIGRATIONS_DIRECTORY), {withFileTypes: true})
51
+ ).filter((ent) => ent.isDirectory())
52
+
53
+ return directories
54
+ .map((ent) => {
55
+ const candidates = resolveMigrationScript(workDir, ent.name)
56
+ const found = candidates.find((candidate) => candidate.mod?.default)
57
+ if (!found) {
58
+ return null
59
+ }
60
+ return {
61
+ dirname: ent.name,
62
+ migration: found.mod.default as Migration,
63
+ }
64
+ })
65
+ .filter(Boolean) as {dirname: string; migration: Migration}[]
66
+ }
67
+
65
68
  export default createMigrationCommand