@stackbit/cms-sanity 0.2.45-develop.1 → 0.2.45-staging.1
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/dist/.tsbuildinfo +1 -1
- package/dist/index.d.ts +1 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js.map +1 -1
- package/dist/sanity-content-source.d.ts +21 -10
- package/dist/sanity-content-source.d.ts.map +1 -1
- package/dist/sanity-content-source.js +74 -242
- package/dist/sanity-content-source.js.map +1 -1
- package/dist/sanity-document-converter.d.ts +11 -9
- package/dist/sanity-document-converter.d.ts.map +1 -1
- package/dist/sanity-document-converter.js +262 -205
- package/dist/sanity-document-converter.js.map +1 -1
- package/dist/sanity-operation-converter.d.ts +60 -0
- package/dist/sanity-operation-converter.d.ts.map +1 -0
- package/dist/sanity-operation-converter.js +664 -0
- package/dist/sanity-operation-converter.js.map +1 -0
- package/dist/sanity-schema-converter.d.ts +35 -3
- package/dist/sanity-schema-converter.d.ts.map +1 -1
- package/dist/sanity-schema-converter.js +290 -43
- package/dist/sanity-schema-converter.js.map +1 -1
- package/dist/sanity-schema-fetcher.d.ts +3 -3
- package/dist/sanity-schema-fetcher.d.ts.map +1 -1
- package/dist/utils.d.ts +53 -0
- package/dist/utils.d.ts.map +1 -1
- package/dist/utils.js +93 -1
- package/dist/utils.js.map +1 -1
- package/package.json +6 -5
- package/src/index.ts +1 -1
- package/src/sanity-content-source.ts +109 -317
- package/src/sanity-document-converter.ts +332 -231
- package/src/sanity-operation-converter.ts +785 -0
- package/src/sanity-schema-converter.ts +424 -70
- package/src/sanity-schema-fetcher.ts +3 -3
- package/src/utils.ts +98 -0
|
@@ -1,54 +1,276 @@
|
|
|
1
1
|
import _ from 'lodash';
|
|
2
|
-
import
|
|
3
|
-
import * as
|
|
4
|
-
import { deepMap, omitByNil } from '@stackbit/utils';
|
|
2
|
+
import * as StackbitTypes from '@stackbit/types';
|
|
3
|
+
import type * as SanityTypes from '@sanity/types';
|
|
4
|
+
import { deepMap, omitByNil, omitByUndefined } from '@stackbit/utils';
|
|
5
5
|
import { resolveLabelFieldForModel } from './utils';
|
|
6
6
|
|
|
7
|
-
//
|
|
7
|
+
// Sanity's cloudinary and other 3rd party DRM plugins add the following models.
|
|
8
|
+
// These models are removed when converting the schema and the fields referencing
|
|
9
|
+
// these models are replaced with our regular "image" field with a "source" property
|
|
10
|
+
// matching to the name of the supported DRM.
|
|
8
11
|
const thirdPartyImageModels = ['cloudinary.asset', 'cloudinary.assetDerived', 'bynder.asset', 'aprimo.asset', 'aprimo.cdnasset'];
|
|
9
|
-
const skipModels = [...thirdPartyImageModels, 'media.tag'];
|
|
12
|
+
const skipModels = [...thirdPartyImageModels, 'media.tag', 'slug', 'markdown', 'json', 'color', 'hslaColor', 'hsvaColor', 'rgbaColor'];
|
|
13
|
+
const internationalizedArrayPrefix = 'internationalizedArray';
|
|
10
14
|
|
|
11
|
-
export
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
15
|
+
export type SchemaContext = null;
|
|
16
|
+
|
|
17
|
+
export type ModelWithContext = StackbitTypes.Model<ModelContext>;
|
|
18
|
+
export type ModelContext = {
|
|
19
|
+
localizedFieldsModelMap?: LocalizedFieldsModelMap;
|
|
20
|
+
fieldAliasMap?: FieldAliasMap;
|
|
21
|
+
} | null;
|
|
22
|
+
|
|
23
|
+
/**
|
|
24
|
+
* Maps model fields to the Internationalized Array types
|
|
25
|
+
* The key is the field path of the field within the model.
|
|
26
|
+
*/
|
|
27
|
+
export type LocalizedFieldsModelMap = Record<
|
|
28
|
+
string,
|
|
29
|
+
{
|
|
30
|
+
arrayModelName: string;
|
|
31
|
+
arrayValueModelName: string;
|
|
32
|
+
}
|
|
33
|
+
>;
|
|
34
|
+
|
|
35
|
+
export type FieldAliasMap = Record<
|
|
36
|
+
string,
|
|
37
|
+
{
|
|
38
|
+
origTypeName: string;
|
|
39
|
+
resolvedTypeName: string;
|
|
40
|
+
}[]
|
|
41
|
+
>;
|
|
42
|
+
|
|
43
|
+
/**
|
|
44
|
+
* Converts Sanity schema to Netlify Create Schema.
|
|
45
|
+
*
|
|
46
|
+
* @param schema Schema as received from sanity-schema-converter.js
|
|
47
|
+
* @param defaultLocale The default locale specified in the SanityContentSource constructor.
|
|
48
|
+
*/
|
|
49
|
+
export function convertSchema({
|
|
50
|
+
schema,
|
|
51
|
+
logger,
|
|
52
|
+
defaultLocale
|
|
53
|
+
}: {
|
|
54
|
+
schema: { models: SanityTypes.SchemaTypeDefinition[] };
|
|
55
|
+
logger: StackbitTypes.Logger;
|
|
56
|
+
defaultLocale?: string;
|
|
57
|
+
}): {
|
|
58
|
+
models: ModelWithContext[];
|
|
59
|
+
locales: StackbitTypes.Locale[];
|
|
60
|
+
} {
|
|
61
|
+
// First, skip all Sanity internal models with names starting with "sanity."
|
|
62
|
+
// and models defined in skipModels.
|
|
63
|
+
const filteredModels = schema.models.filter((model) => {
|
|
64
|
+
return !model.name.startsWith('sanity.') && !skipModels.includes(model.name);
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
// Split models into three groups:
|
|
68
|
+
// 1. Regular "document" and "objets" models that map to Netlify Create's
|
|
69
|
+
// "data" and "object" models respectively.
|
|
70
|
+
// 2. Models with names starting with "internationalizedArray...". These are
|
|
71
|
+
// special models produced by Sanity's internationalized-array plugin
|
|
72
|
+
// https://www.sanity.io/plugins/internationalized-array
|
|
73
|
+
// 3. Type alias models, which are aliases to regular fields like "array",
|
|
74
|
+
// "string", etc.
|
|
75
|
+
const {
|
|
76
|
+
i18nModels = [],
|
|
77
|
+
documentAndObjectModels = [],
|
|
78
|
+
typeAliasModels = []
|
|
79
|
+
} = _.groupBy(filteredModels, (model) => {
|
|
80
|
+
if (model.name.startsWith(internationalizedArrayPrefix)) {
|
|
81
|
+
return 'i18nModels';
|
|
82
|
+
} else if (['document', 'object'].includes(model.type)) {
|
|
83
|
+
return 'documentAndObjectModels';
|
|
84
|
+
} else {
|
|
85
|
+
return 'typeAliasModels';
|
|
86
|
+
}
|
|
87
|
+
}) as unknown as {
|
|
88
|
+
i18nModels: SanityTypes.TypeAliasDefinition<string, SanityTypes.IntrinsicTypeName | undefined>[];
|
|
89
|
+
documentAndObjectModels: (SanityTypes.DocumentDefinition | SanityTypes.ObjectDefinition)[];
|
|
90
|
+
typeAliasModels: SanityTypes.TypeAliasDefinition<string, SanityTypes.IntrinsicTypeName | undefined>[];
|
|
91
|
+
};
|
|
92
|
+
|
|
93
|
+
// Get all locale codes from Internationalized Array models
|
|
94
|
+
const locales = getLocalesFromInternationalizedArrays(i18nModels, defaultLocale);
|
|
95
|
+
// Create map of Internationalized Array
|
|
96
|
+
const { i18nArrayModelMap, i18nValueModelMap } = getLocalizedModelMap(i18nModels);
|
|
97
|
+
|
|
98
|
+
const typeAliasMap = _.keyBy(typeAliasModels, 'name');
|
|
99
|
+
|
|
100
|
+
const models = documentAndObjectModels.map((model): StackbitTypes.DataModel<ModelContext> | StackbitTypes.ObjectModel<ModelContext> => {
|
|
101
|
+
const localizedFieldsModelMap = {};
|
|
102
|
+
const fieldAliasMap = {};
|
|
103
|
+
try {
|
|
104
|
+
const stackbitModel = mapObjectModel({
|
|
105
|
+
model,
|
|
106
|
+
modelFieldPath: [],
|
|
107
|
+
typeAliasMap,
|
|
108
|
+
i18nArrayModelMap,
|
|
109
|
+
localizedFieldsModelMap,
|
|
110
|
+
fieldAliasMap,
|
|
111
|
+
seenAliases: []
|
|
112
|
+
});
|
|
113
|
+
let context: ModelContext = null;
|
|
114
|
+
if (!_.isEmpty(localizedFieldsModelMap) || !_.isEmpty(fieldAliasMap)) {
|
|
115
|
+
context = {
|
|
116
|
+
...(_.isEmpty(localizedFieldsModelMap) ? null : { localizedFieldsModelMap }),
|
|
117
|
+
...(_.isEmpty(fieldAliasMap) ? null : { fieldAliasMap })
|
|
118
|
+
};
|
|
119
|
+
}
|
|
120
|
+
return {
|
|
121
|
+
...stackbitModel,
|
|
122
|
+
context
|
|
123
|
+
};
|
|
124
|
+
} catch (error: any) {
|
|
125
|
+
logger.error(`Error converting model '${model.name}'. ${error.message}`);
|
|
126
|
+
throw error;
|
|
127
|
+
}
|
|
20
128
|
});
|
|
21
|
-
return {
|
|
129
|
+
return {
|
|
130
|
+
models,
|
|
131
|
+
locales
|
|
132
|
+
};
|
|
22
133
|
}
|
|
23
134
|
|
|
24
|
-
|
|
25
|
-
|
|
135
|
+
type CommonProps = {
|
|
136
|
+
modelFieldPath: string[];
|
|
137
|
+
typeAliasMap: Record<string, any>;
|
|
138
|
+
i18nArrayModelMap: Record<string, any>;
|
|
139
|
+
localizedFieldsModelMap: LocalizedFieldsModelMap;
|
|
140
|
+
fieldAliasMap: FieldAliasMap;
|
|
141
|
+
seenAliases: string[];
|
|
142
|
+
};
|
|
143
|
+
type SanityObjectFieldOrArrayItem = SanityIntrinsicFieldOrArrayItem<'object'>;
|
|
144
|
+
type MapObjectModelOptions<Type extends SanityTypes.DocumentDefinition | SanityTypes.ObjectDefinition | SanityObjectFieldOrArrayItem> = {
|
|
145
|
+
model: Type;
|
|
146
|
+
} & CommonProps;
|
|
147
|
+
function mapObjectModel(
|
|
148
|
+
options: MapObjectModelOptions<SanityTypes.DocumentDefinition | SanityTypes.ObjectDefinition>
|
|
149
|
+
): StackbitTypes.DataModel | StackbitTypes.ObjectModel;
|
|
150
|
+
function mapObjectModel(options: MapObjectModelOptions<SanityObjectFieldOrArrayItem>): StackbitTypes.FieldObject;
|
|
151
|
+
function mapObjectModel({
|
|
152
|
+
model,
|
|
153
|
+
...rest
|
|
154
|
+
}: MapObjectModelOptions<SanityTypes.DocumentDefinition | SanityTypes.ObjectDefinition | SanityObjectFieldOrArrayItem>):
|
|
155
|
+
| StackbitTypes.DataModel
|
|
156
|
+
| StackbitTypes.ObjectModel
|
|
157
|
+
| StackbitTypes.FieldObject {
|
|
26
158
|
const modelName = _.get(model, 'name', null);
|
|
27
159
|
const modelLabel = _.get(model, 'title', modelName ? _.startCase(modelName) : null);
|
|
28
|
-
const sanityFieldGroups: FieldGroupItem[] = _.get(model, 'groups', []).map((group: any) => ({ name: group.name, label: group.title }));
|
|
160
|
+
const sanityFieldGroups: StackbitTypes.FieldGroupItem[] = _.get(model, 'groups', []).map((group: any) => ({ name: group.name, label: group.title }));
|
|
29
161
|
const fields = _.get(model, 'fields', []);
|
|
30
|
-
const mappedFields = mapObjectFields(
|
|
162
|
+
const mappedFields = mapObjectFields({
|
|
163
|
+
fields,
|
|
164
|
+
...rest
|
|
165
|
+
});
|
|
31
166
|
|
|
32
167
|
return omitByNil({
|
|
168
|
+
// TODO: ensure type aliases work for documents
|
|
33
169
|
type: getNormalizedModelType(model),
|
|
34
170
|
name: modelName,
|
|
35
171
|
label: modelLabel,
|
|
36
172
|
labelField: resolveLabelFieldForModel(model, 'preview.select.title', mappedFields),
|
|
37
173
|
fieldGroups: sanityFieldGroups,
|
|
38
174
|
fields: mappedFields
|
|
39
|
-
}) as
|
|
175
|
+
}) as StackbitTypes.DataModel | StackbitTypes.ObjectModel | StackbitTypes.FieldObject;
|
|
40
176
|
}
|
|
41
177
|
|
|
42
|
-
function mapObjectFields(fields:
|
|
178
|
+
function mapObjectFields({ fields, modelFieldPath, ...rest }: { fields: SanityTypes.FieldDefinition[] } & CommonProps): StackbitTypes.Field[] {
|
|
43
179
|
return _.map(fields, (field) => {
|
|
44
|
-
return mapField(
|
|
180
|
+
return mapField({
|
|
181
|
+
field,
|
|
182
|
+
modelFieldPath: modelFieldPath.concat(field.name),
|
|
183
|
+
...rest
|
|
184
|
+
});
|
|
45
185
|
});
|
|
46
186
|
}
|
|
47
187
|
|
|
48
|
-
|
|
49
|
-
|
|
188
|
+
/**
|
|
189
|
+
* Maps Sanity FieldDefinition or ArrayOfType to the {@link StackbitTypes.Field}
|
|
190
|
+
* or the {@link StackbitTypes.FieldListItems} respectively.
|
|
191
|
+
*
|
|
192
|
+
* The `mapField()` can be called for object fields or array items.
|
|
193
|
+
* When called for array items, the 'name' property is optional and the 'hidden'
|
|
194
|
+
* attribute is not present.
|
|
195
|
+
*/
|
|
196
|
+
function mapField({
|
|
197
|
+
field,
|
|
198
|
+
modelFieldPath,
|
|
199
|
+
typeAliasMap,
|
|
200
|
+
i18nArrayModelMap,
|
|
201
|
+
localizedFieldsModelMap,
|
|
202
|
+
fieldAliasMap,
|
|
203
|
+
seenAliases
|
|
204
|
+
}: { field: SanityTypes.FieldDefinition | SanityTypes.ArrayOfType } & CommonProps): StackbitTypes.Field {
|
|
205
|
+
let type = _.get(field, 'type');
|
|
50
206
|
const name = _.get(field, 'name');
|
|
51
207
|
const label = _.get(field, 'title', name ? _.startCase(name) : undefined);
|
|
208
|
+
const modelFieldPathStr = modelFieldPath.join('.');
|
|
209
|
+
|
|
210
|
+
let localized: true | undefined;
|
|
211
|
+
// TODO: can the Internationalized Array type have an alias?
|
|
212
|
+
// e.g.: localizedString an alias to internationalizedArrayString?
|
|
213
|
+
// { name: 'localizedString', type: 'internationalizedArrayString' }
|
|
214
|
+
if (type in i18nArrayModelMap) {
|
|
215
|
+
// The localization model map can reference a field definition, or a type alias.
|
|
216
|
+
// i18nArrayModelMap['internationalizedArrayString'] => { valueModelName: 'internationalizedArrayStringValue', valueField: { type: 'string', name: 'value' } }
|
|
217
|
+
// i18nArrayModelMap['internationalizedArrayBoolean'] => { valueModelName: 'internationalizedArrayBooleanValue', valueField: { type: 'boolean', name: 'value' } }
|
|
218
|
+
// i18nArrayModelMap['internationalizedArrayInlineReference'] => { valueModelName: 'internationalizedArrayInlineReferenceValue', valueField: { type: 'reference', to: [...], name: 'value' } }
|
|
219
|
+
// i18nArrayModelMap['internationalizedArrayCustomTypeAlias'] => { valueModelName: 'internationalizedArrayCustomTypeAliasValue', valueField: { type: 'customTypeAlias', name: 'value' } }
|
|
220
|
+
// etc.
|
|
221
|
+
if (seenAliases.includes(type)) {
|
|
222
|
+
throw new Error(`Circular Array aliases are not supported, the Array alias '${type}' is recursively referenced in field '${modelFieldPathStr}'.`);
|
|
223
|
+
}
|
|
224
|
+
seenAliases = seenAliases.concat(type);
|
|
225
|
+
localized = true;
|
|
226
|
+
field = {
|
|
227
|
+
...i18nArrayModelMap[type].valueField,
|
|
228
|
+
...field,
|
|
229
|
+
type: i18nArrayModelMap[type].valueField.type
|
|
230
|
+
};
|
|
231
|
+
localizedFieldsModelMap[modelFieldPathStr] = {
|
|
232
|
+
arrayModelName: type,
|
|
233
|
+
arrayValueModelName: i18nArrayModelMap[type].valueModelName
|
|
234
|
+
};
|
|
235
|
+
type = field.type;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
const visitedTypes: string[] = [];
|
|
239
|
+
let addedAlias = false;
|
|
240
|
+
while (type in typeAliasMap) {
|
|
241
|
+
if (seenAliases.includes(type)) {
|
|
242
|
+
throw new Error(`Circular Array aliases not supported, the Array alias ${type} is recursively referenced in field ${modelFieldPathStr}.`);
|
|
243
|
+
}
|
|
244
|
+
seenAliases = seenAliases.concat(type);
|
|
245
|
+
// In Sanity, the properties of the field, override the properties of
|
|
246
|
+
// the alias. However, the final field type is the type of the alias.
|
|
247
|
+
if (visitedTypes.includes(type)) {
|
|
248
|
+
throw new Error(`Circular type alias detected in field ${modelFieldPathStr}: ${visitedTypes.join(' => ')} => ${type}.`);
|
|
249
|
+
}
|
|
250
|
+
visitedTypes.push(type);
|
|
251
|
+
field = {
|
|
252
|
+
...typeAliasMap[type],
|
|
253
|
+
...field,
|
|
254
|
+
type: typeAliasMap[type].type
|
|
255
|
+
};
|
|
256
|
+
if (!fieldAliasMap[modelFieldPathStr]) {
|
|
257
|
+
fieldAliasMap[modelFieldPathStr] = [];
|
|
258
|
+
}
|
|
259
|
+
const fieldAliases = fieldAliasMap[modelFieldPathStr]!;
|
|
260
|
+
// Non list fields should have only one alias entry.
|
|
261
|
+
// List fields, can have multiple aliases per list item type.
|
|
262
|
+
if (!addedAlias) {
|
|
263
|
+
addedAlias = true;
|
|
264
|
+
fieldAliases.push({
|
|
265
|
+
origTypeName: type,
|
|
266
|
+
resolvedTypeName: field.type
|
|
267
|
+
});
|
|
268
|
+
} else {
|
|
269
|
+
fieldAliases[fieldAliases.length - 1]!.resolvedTypeName = field.type;
|
|
270
|
+
}
|
|
271
|
+
type = field.type;
|
|
272
|
+
}
|
|
273
|
+
|
|
52
274
|
const description = _.get(field, 'description');
|
|
53
275
|
const readOnly = _.get(field, 'readOnly');
|
|
54
276
|
const isRequired = _.get(field, 'validation.isRequired');
|
|
@@ -75,24 +297,30 @@ function mapField(field: any, arrayModelsByName: ContentSourceTypes.ModelMap): F
|
|
|
75
297
|
}
|
|
76
298
|
}
|
|
77
299
|
|
|
78
|
-
const extra = convertField(
|
|
300
|
+
const extra = convertField({
|
|
301
|
+
field,
|
|
302
|
+
modelFieldPath,
|
|
303
|
+
typeAliasMap,
|
|
304
|
+
i18nArrayModelMap,
|
|
305
|
+
localizedFieldsModelMap,
|
|
306
|
+
fieldAliasMap,
|
|
307
|
+
seenAliases
|
|
308
|
+
});
|
|
79
309
|
|
|
80
310
|
return _.assign(
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
_.isUndefined
|
|
95
|
-
),
|
|
311
|
+
omitByUndefined({
|
|
312
|
+
type: null,
|
|
313
|
+
name: name,
|
|
314
|
+
label: label,
|
|
315
|
+
description: description,
|
|
316
|
+
group: group,
|
|
317
|
+
required: isRequired || undefined,
|
|
318
|
+
default: defaultValue,
|
|
319
|
+
const: constValue,
|
|
320
|
+
readOnly: readOnly,
|
|
321
|
+
hidden: hidden,
|
|
322
|
+
localized: localized
|
|
323
|
+
}),
|
|
96
324
|
extra
|
|
97
325
|
);
|
|
98
326
|
}
|
|
@@ -127,12 +355,12 @@ function convertDefaultValue(defaultValue: any) {
|
|
|
127
355
|
);
|
|
128
356
|
}
|
|
129
357
|
|
|
130
|
-
function convertField(field:
|
|
358
|
+
function convertField({ field, ...rest }: { field: SanityTypes.FieldDefinition | SanityTypes.ArrayOfType } & CommonProps): StackbitTypes.FieldSpecificProps {
|
|
131
359
|
const type = _.get(field, 'type');
|
|
132
360
|
if (!_.has(fieldConverterMap, type)) {
|
|
133
|
-
return fieldConverterMap.model(field
|
|
361
|
+
return fieldConverterMap.model({ field: field as SanityAliasFieldOrArrayItemDefinition<'model'>, ...rest });
|
|
134
362
|
}
|
|
135
|
-
return _.get(fieldConverterMap, type)(field,
|
|
363
|
+
return _.get(fieldConverterMap, type)({ field, ...rest });
|
|
136
364
|
}
|
|
137
365
|
|
|
138
366
|
function getEnumOptions(field: any) {
|
|
@@ -149,8 +377,20 @@ function getEnumOptions(field: any) {
|
|
|
149
377
|
}
|
|
150
378
|
}
|
|
151
379
|
|
|
152
|
-
|
|
153
|
-
|
|
380
|
+
type SanityFieldTypes = Exclude<SanityTypes.IntrinsicTypeName, 'document'>;
|
|
381
|
+
type AliasFieldTypes = 'model' | 'color' | 'markdown' | 'json' | 'cloudinary.asset' | 'bynder.asset' | 'aprimo.cdnasset';
|
|
382
|
+
type SanityIntrinsicFieldOrArrayItem<Type extends SanityFieldTypes> =
|
|
383
|
+
| (SanityTypes.InlineFieldDefinition[Type] & SanityTypes.FieldDefinitionBase)
|
|
384
|
+
| SanityTypes.IntrinsicArrayOfDefinition[Type];
|
|
385
|
+
type SanityAliasFieldOrArrayItemDefinition<Type extends string> =
|
|
386
|
+
| (SanityTypes.TypeAliasDefinition<Type, SanityTypes.IntrinsicTypeName | undefined> & SanityTypes.FieldDefinitionBase)
|
|
387
|
+
| SanityTypes.ArrayOfEntry<SanityTypes.TypeAliasDefinition<Type, SanityTypes.IntrinsicTypeName | undefined>>;
|
|
388
|
+
const fieldConverterMap: {
|
|
389
|
+
[Type in SanityFieldTypes]: (options: { field: SanityIntrinsicFieldOrArrayItem<Type> } & CommonProps) => StackbitTypes.FieldSpecificProps;
|
|
390
|
+
} & {
|
|
391
|
+
[Type in AliasFieldTypes]: (options: { field: SanityAliasFieldOrArrayItemDefinition<Type> } & CommonProps) => StackbitTypes.FieldSpecificProps;
|
|
392
|
+
} = {
|
|
393
|
+
string: ({ field }) => {
|
|
154
394
|
const options = getEnumOptions(field);
|
|
155
395
|
if (options) {
|
|
156
396
|
return { type: 'enum', options: options };
|
|
@@ -167,6 +407,9 @@ const fieldConverterMap = {
|
|
|
167
407
|
text: () => {
|
|
168
408
|
return { type: 'text' };
|
|
169
409
|
},
|
|
410
|
+
email: () => {
|
|
411
|
+
return { type: 'string' };
|
|
412
|
+
},
|
|
170
413
|
color: () => {
|
|
171
414
|
return { type: 'color' };
|
|
172
415
|
},
|
|
@@ -176,21 +419,15 @@ const fieldConverterMap = {
|
|
|
176
419
|
block: () => {
|
|
177
420
|
return { type: 'richText' };
|
|
178
421
|
},
|
|
179
|
-
|
|
180
|
-
// TODO: implement when we will handle parsing of rich text
|
|
181
|
-
},
|
|
182
|
-
number: (field: any) => {
|
|
422
|
+
number: ({ field }) => {
|
|
183
423
|
const validation = _.get(field, 'validation');
|
|
184
424
|
const isInteger = _.get(validation, 'isInteger');
|
|
185
|
-
return
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
},
|
|
192
|
-
_.isNil
|
|
193
|
-
);
|
|
425
|
+
return omitByNil({
|
|
426
|
+
type: 'number',
|
|
427
|
+
subtype: isInteger ? 'int' : 'float',
|
|
428
|
+
min: _.get(validation, 'min'),
|
|
429
|
+
max: _.get(validation, 'max')
|
|
430
|
+
});
|
|
194
431
|
},
|
|
195
432
|
boolean: () => {
|
|
196
433
|
return { type: 'boolean' };
|
|
@@ -219,26 +456,43 @@ const fieldConverterMap = {
|
|
|
219
456
|
return { type: 'image', source: 'aprimo' };
|
|
220
457
|
},
|
|
221
458
|
geopoint: () => {
|
|
222
|
-
return { type: 'geopoint' };
|
|
459
|
+
return { type: 'model', models: ['geopoint'] };
|
|
223
460
|
},
|
|
224
|
-
reference: (field
|
|
461
|
+
reference: ({ field }) => {
|
|
225
462
|
const toItems = _.castArray(_.get(field, 'to', []));
|
|
226
463
|
return {
|
|
227
464
|
type: 'reference',
|
|
228
465
|
models: _.map(toItems, (item) => item.type)
|
|
229
466
|
};
|
|
230
467
|
},
|
|
468
|
+
crossDatasetReference: ({ field }) => {
|
|
469
|
+
// TODO: implement cross-reference
|
|
470
|
+
// Sanity crossDatasetReference fields can reference between datasets
|
|
471
|
+
// of the same project. But Stackbit cross-reference fields cannot
|
|
472
|
+
// differentiate environments of the same project as the object in the
|
|
473
|
+
// models array only contains the srcType and srcProjectId
|
|
474
|
+
return {
|
|
475
|
+
type: 'cross-reference',
|
|
476
|
+
models: []
|
|
477
|
+
// models: field.to.map((toItem) => {
|
|
478
|
+
// return {
|
|
479
|
+
// modelName: toItem.type,
|
|
480
|
+
// srcType: 'sanity',
|
|
481
|
+
// srcProjectId: '...', // the projectId should be the same as the current one
|
|
482
|
+
// // Stackbit cross-references do not have a way to specify different environment
|
|
483
|
+
// srcEnvironment: field.dataset
|
|
484
|
+
// }
|
|
485
|
+
// })
|
|
486
|
+
};
|
|
487
|
+
},
|
|
231
488
|
json: () => {
|
|
232
489
|
return { type: 'json' };
|
|
233
490
|
},
|
|
234
|
-
object: (field
|
|
235
|
-
return mapObjectModel(field,
|
|
491
|
+
object: ({ field, ...rest }) => {
|
|
492
|
+
return mapObjectModel({ model: field, ...rest }) as StackbitTypes.FieldObjectProps;
|
|
236
493
|
},
|
|
237
|
-
model: (field
|
|
494
|
+
model: ({ field }) => {
|
|
238
495
|
const type = _.get(field, 'type');
|
|
239
|
-
if (_.has(arrayModelsByName, type)) {
|
|
240
|
-
return fieldConverterMap.array(arrayModelsByName[type], arrayModelsByName);
|
|
241
|
-
}
|
|
242
496
|
if (thirdPartyImageModels.includes(type)) {
|
|
243
497
|
const fn = (fieldConverterMap[type as keyof typeof fieldConverterMap] as any) ?? fieldConverterMap.image;
|
|
244
498
|
return fn();
|
|
@@ -281,7 +535,7 @@ const fieldConverterMap = {
|
|
|
281
535
|
* }]
|
|
282
536
|
* }
|
|
283
537
|
*/
|
|
284
|
-
array: (field
|
|
538
|
+
array: ({ field, ...rest }) => {
|
|
285
539
|
const options = getEnumOptions(field);
|
|
286
540
|
if (options) {
|
|
287
541
|
return {
|
|
@@ -298,13 +552,38 @@ const fieldConverterMap = {
|
|
|
298
552
|
return { type: 'richText' };
|
|
299
553
|
}
|
|
300
554
|
const items = _.map(ofItems, (item, index) => {
|
|
301
|
-
|
|
555
|
+
const { modelFieldPath, ...props } = rest;
|
|
556
|
+
const listModelFieldPath = modelFieldPath.concat('items');
|
|
557
|
+
const modelFieldPathStr = listModelFieldPath.join('.');
|
|
558
|
+
// Sanity array fields may consist of anonymous 'object' with names.
|
|
559
|
+
// When creating such objects, the '_type' should be set to their
|
|
560
|
+
// name to identify them among other 'object' types.
|
|
561
|
+
if (item && 'name' in item && item.name && 'type' in item && item.type === 'object') {
|
|
562
|
+
if (!props.fieldAliasMap[modelFieldPathStr]) {
|
|
563
|
+
props.fieldAliasMap[modelFieldPathStr] = [];
|
|
564
|
+
}
|
|
565
|
+
props.fieldAliasMap[modelFieldPathStr]!.push({
|
|
566
|
+
origTypeName: item.name,
|
|
567
|
+
resolvedTypeName: 'object'
|
|
568
|
+
});
|
|
569
|
+
}
|
|
570
|
+
return mapField({
|
|
571
|
+
field: item,
|
|
572
|
+
modelFieldPath: listModelFieldPath,
|
|
573
|
+
...props
|
|
574
|
+
});
|
|
302
575
|
});
|
|
303
576
|
let modelNames: string[] = [];
|
|
304
577
|
let referenceModelName: string[] = [];
|
|
305
578
|
const consolidatedItems = [];
|
|
306
579
|
_.forEach(items, (item) => {
|
|
307
580
|
const type = _.get(item, 'type');
|
|
581
|
+
// If the converted items have only two properties
|
|
582
|
+
// - type: 'model' and models: [...],
|
|
583
|
+
// - type: 'reference' and models: [...]
|
|
584
|
+
// Then, consolidate all their models under the same 'model' or
|
|
585
|
+
// 'reference' item. Otherwise, if the field has additional properties
|
|
586
|
+
// like, "name", "label", etc., then add it as a separate items.
|
|
308
587
|
if (type === 'model' && _.has(item, 'models') && _.size(item) === 2) {
|
|
309
588
|
modelNames = modelNames.concat(_.get(item, 'models'));
|
|
310
589
|
} else if (type === 'reference' && _.has(item, 'models') && _.size(item) === 2) {
|
|
@@ -329,12 +608,12 @@ const fieldConverterMap = {
|
|
|
329
608
|
return {
|
|
330
609
|
type: 'list',
|
|
331
610
|
items: _.head(consolidatedItems)
|
|
332
|
-
};
|
|
611
|
+
} as StackbitTypes.FieldList;
|
|
333
612
|
}
|
|
334
613
|
return {
|
|
335
614
|
type: 'list',
|
|
336
615
|
items: consolidatedItems
|
|
337
|
-
};
|
|
616
|
+
} as unknown as StackbitTypes.FieldList;
|
|
338
617
|
}
|
|
339
618
|
};
|
|
340
619
|
|
|
@@ -342,3 +621,78 @@ function getNormalizedModelType(sanityModel: any) {
|
|
|
342
621
|
const modelType = _.get(sanityModel, 'type');
|
|
343
622
|
return modelType === 'document' ? 'data' : 'object';
|
|
344
623
|
}
|
|
624
|
+
|
|
625
|
+
function getLocalesFromInternationalizedArrays(internationalizedArrayModels: any, defaultLocale?: string): StackbitTypes.Locale[] {
|
|
626
|
+
const localeMap = internationalizedArrayModels
|
|
627
|
+
.filter((model: any) => model.type === 'array')
|
|
628
|
+
.reduce((localeMap: Record<string, string>, model: any) => {
|
|
629
|
+
if (model?.options?.languages && Array.isArray(model?.options?.languages)) {
|
|
630
|
+
return model?.options?.languages.reduce((localeMap: Record<string, string>, locale: { id: string; title: string }) => {
|
|
631
|
+
if (!(locale.id in localeMap)) {
|
|
632
|
+
localeMap[locale.id] = locale.title;
|
|
633
|
+
}
|
|
634
|
+
return localeMap;
|
|
635
|
+
}, localeMap);
|
|
636
|
+
}
|
|
637
|
+
return localeMap;
|
|
638
|
+
}, {});
|
|
639
|
+
let defaultLocaleFound = false;
|
|
640
|
+
const locales = Object.entries(localeMap).map(([localeId, localeTitle]) => {
|
|
641
|
+
const locale: StackbitTypes.Locale = {
|
|
642
|
+
code: localeId
|
|
643
|
+
};
|
|
644
|
+
if (defaultLocale && localeId === defaultLocale) {
|
|
645
|
+
defaultLocaleFound = true;
|
|
646
|
+
locale.default = true;
|
|
647
|
+
}
|
|
648
|
+
return locale;
|
|
649
|
+
});
|
|
650
|
+
if (!defaultLocaleFound && locales.length > 0) {
|
|
651
|
+
locales[0]!.default = true;
|
|
652
|
+
}
|
|
653
|
+
return locales;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
function getLocalizedModelMap(i18nModels: any[]): {
|
|
657
|
+
i18nArrayModelMap: Record<
|
|
658
|
+
string,
|
|
659
|
+
{
|
|
660
|
+
valueModelName: string;
|
|
661
|
+
valueField: any;
|
|
662
|
+
}
|
|
663
|
+
>;
|
|
664
|
+
i18nValueModelMap: Record<string, any>;
|
|
665
|
+
} {
|
|
666
|
+
const [i18nArrayModels, i18nObjectModels] = _.partition(i18nModels, { type: 'array' });
|
|
667
|
+
const i18nObjectModelsByName = _.keyBy(i18nObjectModels, 'name');
|
|
668
|
+
return _.reduce(
|
|
669
|
+
i18nArrayModels,
|
|
670
|
+
(accum, i18nArrayModel) => {
|
|
671
|
+
if (Array.isArray(i18nArrayModel.of) && i18nArrayModel.of.length === 1) {
|
|
672
|
+
const localizedArrayOfItem = i18nArrayModel.of[0];
|
|
673
|
+
if (localizedArrayOfItem && localizedArrayOfItem.type in i18nObjectModelsByName) {
|
|
674
|
+
const valueModelName = localizedArrayOfItem.type;
|
|
675
|
+
const localizedArrayValueModel = i18nObjectModelsByName[valueModelName];
|
|
676
|
+
const valueField = localizedArrayValueModel.fields[0];
|
|
677
|
+
// valueField.name === 'value'
|
|
678
|
+
accum.i18nArrayModelMap[i18nArrayModel.name] = {
|
|
679
|
+
valueModelName: valueModelName,
|
|
680
|
+
valueField: valueField
|
|
681
|
+
};
|
|
682
|
+
accum.i18nValueModelMap[valueModelName] = valueField;
|
|
683
|
+
}
|
|
684
|
+
}
|
|
685
|
+
return accum;
|
|
686
|
+
},
|
|
687
|
+
{
|
|
688
|
+
i18nArrayModelMap: {} as Record<
|
|
689
|
+
string,
|
|
690
|
+
{
|
|
691
|
+
valueModelName: string;
|
|
692
|
+
valueField: any;
|
|
693
|
+
}
|
|
694
|
+
>,
|
|
695
|
+
i18nValueModelMap: {} as Record<string, any>
|
|
696
|
+
}
|
|
697
|
+
);
|
|
698
|
+
}
|
|
@@ -3,7 +3,7 @@ import * as path from 'path';
|
|
|
3
3
|
import _ from 'lodash';
|
|
4
4
|
import fse from 'fs-extra';
|
|
5
5
|
import { deepMap } from '@stackbit/utils';
|
|
6
|
-
import * as
|
|
6
|
+
import * as StackbitTypes from '@stackbit/types';
|
|
7
7
|
|
|
8
8
|
export async function fetchSchemaLegacy({ studioPath }: { studioPath: string }) {
|
|
9
9
|
const getSanitySchema = require('@sanity/core/lib/actions/graphql/getSanitySchema');
|
|
@@ -176,8 +176,8 @@ export interface FetchSchemaOptions {
|
|
|
176
176
|
studioPath: string;
|
|
177
177
|
repoPath: string;
|
|
178
178
|
nodePath?: string;
|
|
179
|
-
spawnRunner?:
|
|
180
|
-
logger?:
|
|
179
|
+
spawnRunner?: StackbitTypes.UserCommandSpawner;
|
|
180
|
+
logger?: StackbitTypes.Logger;
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
export function spawnFetchSchema({ studioPath, nodePath, repoPath, spawnRunner, logger }: FetchSchemaOptions): Promise<any> {
|