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.
- package/lib/_chunks/{_internal-2CJ5wSKF.js → _internal-6Pl2wJGj.js} +422 -201
- package/lib/_chunks/_internal-6Pl2wJGj.js.map +1 -0
- package/lib/_chunks/{_internal-79flWvL8.js → _internal-yQwMw2ht.js} +419 -203
- package/lib/_chunks/_internal-yQwMw2ht.js.map +1 -0
- package/lib/_chunks/{_internalBrowser-QAFz3SKp.js → _internalBrowser-HWvRXvlU.js} +22 -22
- package/lib/_chunks/_internalBrowser-HWvRXvlU.js.map +1 -0
- package/lib/_chunks/{_internalBrowser-Y0qKZ-GA.js → _internalBrowser-TWIhitgI.js} +22 -22
- package/lib/_chunks/_internalBrowser-TWIhitgI.js.map +1 -0
- package/lib/_chunks/{deployApiAction-SHteit1G.js → deployApiAction-TZcCtXan.js} +2 -2
- package/lib/_chunks/{deployApiAction-SHteit1G.js.map → deployApiAction-TZcCtXan.js.map} +1 -1
- package/lib/_chunks/{deployApiAction-bRyJpGOS.js → deployApiAction-rVL67VYW.js} +2 -2
- package/lib/_chunks/{deployApiAction-bRyJpGOS.js.map → deployApiAction-rVL67VYW.js.map} +1 -1
- package/lib/_chunks/{getStudioConfig-JSkc4GE0.js → getStudioWorkspaces-HX9o9-hl.js} +20 -4
- package/lib/_chunks/getStudioWorkspaces-HX9o9-hl.js.map +1 -0
- package/lib/_chunks/{index-EO9iRnDS.js → index-2kSxso3r.js} +2 -2
- package/lib/_chunks/{index-EO9iRnDS.js.map → index-2kSxso3r.js.map} +1 -1
- package/lib/_chunks/{index-FQCCBbuC.js → index-751ZLh3z.js} +6 -6
- package/lib/_chunks/{index-FQCCBbuC.js.map → index-751ZLh3z.js.map} +1 -1
- package/lib/_chunks/{index-VNSHvpZr.js → index-HcF369ru.js} +2 -2
- package/lib/_chunks/{index-VNSHvpZr.js.map → index-HcF369ru.js.map} +1 -1
- package/lib/_chunks/{index-Xs2xnLUV.js → index-MAAxgUnl.js} +6 -6
- package/lib/_chunks/{index-Xs2xnLUV.js.map → index-MAAxgUnl.js.map} +1 -1
- package/lib/_chunks/{index-AaK2CidU.js → index-NweJPfGb.js} +2 -2
- package/lib/_chunks/{index-AaK2CidU.js.map → index-NweJPfGb.js.map} +1 -1
- package/lib/_chunks/{index-R7R6AyHF.js → index-zobOqko7.js} +2 -2
- package/lib/_chunks/{index-R7R6AyHF.js.map → index-zobOqko7.js.map} +1 -1
- package/lib/_chunks/{listApisAction-6lGkFZrU.js → listApisAction-IvKV4iAd.js} +2 -2
- package/lib/_chunks/{listApisAction-6lGkFZrU.js.map → listApisAction-IvKV4iAd.js.map} +1 -1
- package/lib/_chunks/{listApisAction-CqCkBz-F.js → listApisAction-oca2uykY.js} +2 -2
- package/lib/_chunks/{listApisAction-CqCkBz-F.js.map → listApisAction-oca2uykY.js.map} +1 -1
- package/lib/_chunks/pane-9HEeITpx.js +5 -0
- package/lib/_chunks/pane-9HEeITpx.js.map +1 -0
- package/lib/_chunks/pane-QyVrOLqS.js +2 -0
- package/lib/_chunks/pane-QyVrOLqS.js.map +1 -0
- package/lib/_chunks/pane-TXXUUvdc.js +5 -0
- package/lib/_chunks/pane-TXXUUvdc.js.map +1 -0
- package/lib/_chunks/pane-cQxQtBcL.js +2 -0
- package/lib/_chunks/pane-cQxQtBcL.js.map +1 -0
- package/lib/_chunks/{structure-qJLnDJXq.js → structure-iqIDIH7-.js} +3 -3
- package/lib/_chunks/structure-iqIDIH7-.js.map +1 -0
- package/lib/_chunks/{structure-588eAwLd.js → structure-o_wMXC_G.js} +3 -3
- package/lib/_chunks/structure-o_wMXC_G.js.map +1 -0
- package/lib/_chunks/validateAction-4Jl_y_iB.js +154 -0
- package/lib/_chunks/validateAction-4Jl_y_iB.js.map +1 -0
- package/lib/_chunks/{validateAction-GUvMkXiN.js → validateAction-6q8Sqwaz.js} +25 -73
- package/lib/_chunks/validateAction-6q8Sqwaz.js.map +1 -0
- package/lib/_chunks/validateAction-Bz67ApRP.js +143 -0
- package/lib/_chunks/validateAction-Bz67ApRP.js.map +1 -0
- package/lib/_chunks/{validateAction-NIrqtyYJ.js → validateAction-shi462sq.js} +27 -75
- package/lib/_chunks/validateAction-shi462sq.js.map +1 -0
- package/lib/_internal/cli/threads/getGraphQLAPIs.js +2 -2
- package/lib/_internal/cli/threads/getGraphQLAPIs.js.map +1 -1
- package/lib/_internal/cli/threads/validateDocuments.js +5 -3
- package/lib/_internal/cli/threads/validateDocuments.js.map +1 -1
- package/lib/_internal/cli/threads/validateSchema.js +55 -0
- package/lib/_internal/cli/threads/validateSchema.js.map +1 -0
- package/lib/_internal.esm.js +1 -1
- package/lib/_internal.js +1 -1
- package/lib/_internalBrowser.esm.js +1 -1
- package/lib/_internalBrowser.js +1 -1
- package/lib/desk.esm.js +1 -1
- package/lib/desk.js +1 -1
- package/lib/exports/index.d.ts +25 -0
- package/lib/index.cjs.mjs +1 -0
- package/lib/index.esm.js +2 -2
- package/lib/index.js +2 -1
- package/lib/index.js.map +1 -1
- package/lib/structure.esm.js +1 -1
- package/lib/structure.js +1 -1
- package/package.json +15 -15
- package/src/_internal/cli/actions/schema/formatSchemaValidation.ts +96 -0
- package/src/_internal/cli/actions/schema/validateAction.ts +120 -0
- package/src/_internal/cli/actions/validation/reporters/prettyReporter/formatDocumentValidation.ts +17 -95
- package/src/_internal/cli/commands/index.ts +2 -0
- package/src/_internal/cli/commands/migration/createMigrationCommand.ts +18 -10
- package/src/_internal/cli/commands/migration/listMigrationsCommand.ts +34 -31
- package/src/_internal/cli/commands/migration/prettyMutationFormatter.ts +214 -0
- package/src/_internal/cli/commands/migration/runMigrationCommand.ts +102 -58
- package/src/_internal/cli/commands/migration/templates/minimalAdvanced.ts +1 -1
- package/src/_internal/cli/commands/migration/templates/minimalSimple.ts +1 -1
- package/src/_internal/cli/commands/migration/templates/renameField.ts +3 -3
- package/src/_internal/cli/commands/migration/templates/renameType.ts +1 -1
- package/src/_internal/cli/commands/migration/templates/stringToPTE.ts +1 -1
- package/src/_internal/cli/commands/migration/utils/mutationFormatter.ts +1 -1
- package/src/_internal/cli/commands/schema/validateSchemaCommand.ts +35 -0
- package/src/_internal/cli/threads/getGraphQLAPIs.ts +2 -2
- package/src/_internal/cli/threads/validateDocuments.ts +6 -3
- package/src/_internal/cli/threads/validateSchema.ts +73 -0
- package/src/_internal/cli/util/{getStudioConfig.ts → getStudioWorkspaces.ts} +30 -8
- package/src/_internal/cli/util/tree.ts +110 -0
- package/src/core/config/index.ts +1 -0
- package/src/core/config/prepareConfig.ts +2 -5
- package/src/core/config/resolveSchemaTypes.ts +29 -0
- package/src/core/studio/screens/schemaErrors/SchemaProblemGroups.tsx +4 -2
- package/src/structure/comments/src/components/pte/blocks/MentionInlineBlock.tsx +13 -6
- package/lib/_chunks/_internal-2CJ5wSKF.js.map +0 -1
- package/lib/_chunks/_internal-79flWvL8.js.map +0 -1
- package/lib/_chunks/_internalBrowser-QAFz3SKp.js.map +0 -1
- package/lib/_chunks/_internalBrowser-Y0qKZ-GA.js.map +0 -1
- package/lib/_chunks/getStudioConfig-JSkc4GE0.js.map +0 -1
- package/lib/_chunks/pane-QmJb9ZTN.js +0 -2
- package/lib/_chunks/pane-QmJb9ZTN.js.map +0 -1
- package/lib/_chunks/pane-SK7FWNTJ.js +0 -2
- package/lib/_chunks/pane-SK7FWNTJ.js.map +0 -1
- package/lib/_chunks/pane-y4hpcKe1.js +0 -5
- package/lib/_chunks/pane-y4hpcKe1.js.map +0 -1
- package/lib/_chunks/pane-yQXBQyyI.js +0 -5
- package/lib/_chunks/pane-yQXBQyyI.js.map +0 -1
- package/lib/_chunks/structure-588eAwLd.js.map +0 -1
- package/lib/_chunks/structure-qJLnDJXq.js.map +0 -1
- package/lib/_chunks/validateAction-GUvMkXiN.js.map +0 -1
- package/lib/_chunks/validateAction-NIrqtyYJ.js.map +0 -1
package/lib/structure.esm.js
CHANGED
@@ -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-
|
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-
|
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.
|
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.
|
203
|
-
"@sanity/cli": "3.26.2-canary.
|
204
|
-
"@sanity/client": "^6.
|
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.
|
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.
|
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.
|
213
|
+
"@sanity/import": "3.26.2-canary.69+747ad532f2",
|
214
214
|
"@sanity/logos": "^2.1.4",
|
215
|
-
"@sanity/migrate": "3.26.2-canary.
|
216
|
-
"@sanity/mutator": "3.26.2-canary.
|
217
|
-
"@sanity/portable-text-editor": "3.26.2-canary.
|
218
|
-
"@sanity/presentation": "1.7.
|
219
|
-
"@sanity/schema": "3.26.2-canary.
|
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.
|
221
|
+
"@sanity/types": "3.26.2-canary.69+747ad532f2",
|
222
222
|
"@sanity/ui": "^2.0.0",
|
223
|
-
"@sanity/util": "3.26.2-canary.
|
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": "
|
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
|
+
}
|
package/src/_internal/cli/actions/validation/reporters/prettyReporter/formatDocumentValidation.ts
CHANGED
@@ -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
|
13
|
-
|
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.
|
40
|
+
if (!root.nodes) return ''
|
94
41
|
|
95
|
-
const [first, ...rest] = root.
|
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
|
-
|
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: '[
|
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
|
42
|
-
|
43
|
-
|
48
|
+
let [title] = args.argsWithoutOptions
|
49
|
+
|
50
|
+
while (!title?.trim()) {
|
51
|
+
title = await prompt.single({
|
44
52
|
type: 'input',
|
45
|
-
suffix: ' (e.g.
|
46
|
-
message: '
|
53
|
+
suffix: ' (e.g. "Rename field from location to address")',
|
54
|
+
message: 'Title of migration',
|
47
55
|
})
|
48
|
-
if (!
|
49
|
-
output.
|
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(
|
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:
|
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 {
|
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 ${
|
27
|
+
title: `Found ${migrations.length} migrations in project`,
|
51
28
|
columns: [
|
52
29
|
{name: 'id', title: 'ID', alignment: 'left'},
|
53
|
-
{name: '
|
30
|
+
{name: 'title', title: 'Title', alignment: 'left'},
|
54
31
|
],
|
55
32
|
})
|
56
33
|
|
57
|
-
|
58
|
-
table.addRow({id: definedMigration.dirname,
|
34
|
+
migrations.forEach((definedMigration) => {
|
35
|
+
table.addRow({id: definedMigration.dirname, title: definedMigration.migration.title})
|
59
36
|
})
|
60
37
|
table.printTable()
|
61
|
-
output.print(
|
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
|