@stackbit/sdk 0.3.5 → 0.3.6

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 (56) hide show
  1. package/dist/config/config-loader-static.js +1 -1
  2. package/dist/config/config-loader-static.js.map +1 -1
  3. package/dist/config/config-loader-utils.d.ts +12 -7
  4. package/dist/config/config-loader-utils.d.ts.map +1 -1
  5. package/dist/config/config-loader-utils.js +104 -68
  6. package/dist/config/config-loader-utils.js.map +1 -1
  7. package/dist/config/config-loader.d.ts +46 -27
  8. package/dist/config/config-loader.d.ts.map +1 -1
  9. package/dist/config/config-loader.js +294 -265
  10. package/dist/config/config-loader.js.map +1 -1
  11. package/dist/config/config-schema.d.ts +2 -2
  12. package/dist/config/config-schema.d.ts.map +1 -1
  13. package/dist/config/config-schema.js +123 -55
  14. package/dist/config/config-schema.js.map +1 -1
  15. package/dist/config/config-types.d.ts +4 -3
  16. package/dist/config/config-types.d.ts.map +1 -1
  17. package/dist/config/config-validator.d.ts +9 -5
  18. package/dist/config/config-validator.d.ts.map +1 -1
  19. package/dist/config/config-validator.js +42 -23
  20. package/dist/config/config-validator.js.map +1 -1
  21. package/dist/config/presets-loader.d.ts +13 -4
  22. package/dist/config/presets-loader.d.ts.map +1 -1
  23. package/dist/config/presets-loader.js +49 -23
  24. package/dist/config/presets-loader.js.map +1 -1
  25. package/dist/content/content-schema.js +1 -1
  26. package/dist/content/content-schema.js.map +1 -1
  27. package/dist/index.d.ts +4 -2
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +12 -2
  30. package/dist/index.js.map +1 -1
  31. package/dist/utils/index.d.ts +1 -8
  32. package/dist/utils/index.d.ts.map +1 -1
  33. package/dist/utils/index.js.map +1 -1
  34. package/dist/utils/model-extender.js +1 -1
  35. package/dist/utils/model-extender.js.map +1 -1
  36. package/dist/utils/model-iterators.d.ts +3 -2
  37. package/dist/utils/model-iterators.d.ts.map +1 -1
  38. package/dist/utils/model-iterators.js.map +1 -1
  39. package/dist/utils/model-utils.d.ts +9 -8
  40. package/dist/utils/model-utils.d.ts.map +1 -1
  41. package/dist/utils/model-utils.js +26 -9
  42. package/dist/utils/model-utils.js.map +1 -1
  43. package/package.json +3 -3
  44. package/src/config/config-loader-static.ts +1 -1
  45. package/src/config/config-loader-utils.ts +111 -78
  46. package/src/config/config-loader.ts +457 -394
  47. package/src/config/config-schema.ts +150 -81
  48. package/src/config/config-types.ts +6 -3
  49. package/src/config/config-validator.ts +51 -29
  50. package/src/config/presets-loader.ts +59 -30
  51. package/src/content/content-schema.ts +1 -1
  52. package/src/index.ts +21 -2
  53. package/src/utils/index.ts +1 -13
  54. package/src/utils/model-extender.ts +1 -1
  55. package/src/utils/model-iterators.ts +6 -5
  56. package/src/utils/model-utils.ts +38 -16
@@ -2,15 +2,11 @@ import _ from 'lodash';
2
2
  import path from 'path';
3
3
  import fse from 'fs-extra';
4
4
  import { parseFile, append } from '@stackbit/utils';
5
+ import { ModelWithSource } from '@stackbit/types';
5
6
 
6
- import { Config, Preset } from './config-types';
7
+ import { Config, Model, Preset } from './config-types';
7
8
  import { ConfigPresetsError } from './config-errors';
8
9
 
9
- export interface PresetsLoaderResult {
10
- config: Config;
11
- errors: ConfigPresetsError[];
12
- }
13
-
14
10
  interface RawPresetData {
15
11
  model: string;
16
12
  srcType: string;
@@ -24,16 +20,23 @@ interface RawPreset {
24
20
  data: Record<string, any>;
25
21
  }
26
22
 
27
- export async function loadPresets(dirPath: string, config: Config): Promise<PresetsLoaderResult> {
23
+ export async function loadPresets({
24
+ config,
25
+ fallbackSrcType,
26
+ fallbackSrcProjectId
27
+ }: {
28
+ config: Config;
29
+ fallbackSrcType?: string;
30
+ fallbackSrcProjectId?: string;
31
+ }): Promise<{
32
+ presets: Record<string, Preset>;
33
+ errors: ConfigPresetsError[];
34
+ }> {
28
35
  const presetFiles = [];
29
- let presetsRelDirs = ['.stackbit/presets', 'node_modules/@stackbit/components/presets'];
30
-
31
- if (config.presetSource?.type === 'files' && config.presetSource?.presetDirs) {
32
- presetsRelDirs = [...new Set([...presetsRelDirs, ...config.presetSource.presetDirs])];
33
- }
36
+ const presetsRelDirs = getPresetDirs(config);
34
37
 
35
38
  for (const presetsRelDir of presetsRelDirs) {
36
- const presetsDir = path.join(dirPath, presetsRelDir);
39
+ const presetsDir = path.join(config.dirPath, presetsRelDir);
37
40
  if (!(await fse.pathExists(presetsDir))) {
38
41
  continue;
39
42
  }
@@ -44,12 +47,11 @@ export async function loadPresets(dirPath: string, config: Config): Promise<Pres
44
47
  }
45
48
 
46
49
  const presets: Record<string, Preset> = {};
47
- const presetsIdsByModel: Record<string, string[]> = {};
48
50
  const errors: ConfigPresetsError[] = [];
49
51
 
50
52
  for (const presetFile of presetFiles) {
51
53
  const presetsRelDir = path.dirname(presetFile);
52
- const presetPath = path.join(dirPath, presetFile);
54
+ const presetPath = path.join(config.dirPath, presetFile);
53
55
  let presetData: RawPresetData;
54
56
  try {
55
57
  presetData = await parseFile(presetPath);
@@ -57,6 +59,8 @@ export async function loadPresets(dirPath: string, config: Config): Promise<Pres
57
59
  errors.push(new ConfigPresetsError(`Error parsing ${presetFile} (${err?.message})`));
58
60
  continue;
59
61
  }
62
+ const srcType = presetData.srcType ?? fallbackSrcType;
63
+ const srcProjectId = presetData.srcProjectId ?? fallbackSrcProjectId;
60
64
  _.forEach(_.get(presetData, 'presets', []), (preset, i) => {
61
65
  const presetId = `${presetFile}:presets[${i}]`;
62
66
  const { thumbnail, ...rest } = preset;
@@ -64,29 +68,54 @@ export async function loadPresets(dirPath: string, config: Config): Promise<Pres
64
68
  ...rest,
65
69
  ...(thumbnail ? { thumbnail: resolveThumbnailPath(thumbnail, presetsRelDir) } : null),
66
70
  modelName: presetData.model,
67
- srcType: presetData.srcType,
68
- srcProjectId: presetData.srcProjectId
69
- };
70
- append(presetsIdsByModel, presetData.model, presetId);
71
+ ...(srcType ? { srcType } : null),
72
+ ...(srcProjectId ? { srcProjectId } : null),
73
+ } as Preset;
71
74
  });
72
75
  }
73
76
 
77
+ return {
78
+ presets,
79
+ errors
80
+ };
81
+ }
82
+
83
+ export function getPresetDirs(config: Config): string[] {
84
+ const defaultPresetsDirs = ['.stackbit/presets', 'node_modules/@stackbit/components/presets'];
85
+ if (config.presetSource?.type === 'files' && config.presetSource?.presetDirs) {
86
+ return [...new Set([...defaultPresetsDirs, ...config.presetSource.presetDirs])];
87
+ }
88
+ return defaultPresetsDirs;
89
+ }
90
+
91
+ export function extendModelsWithPresetsIds<T extends Model | ModelWithSource>({ models, presets }: { models: T[]; presets: Record<string, Preset> }): T[] {
92
+ // for older projects that pass the Model, we assume that all presets belong to the same content-source
93
+ const presetsIdsByContentSourceAndModel: Record<string, Record<string, Record<string, string[]>>> = {};
94
+ const presetsIdsByModel_deprecated: Record<string, string[]> = {};
95
+ for (const [presetId, preset] of Object.entries(presets)) {
96
+ append(presetsIdsByContentSourceAndModel, [preset.srcType, preset.srcProjectId, preset.modelName], presetId);
97
+ append(presetsIdsByModel_deprecated, preset.modelName, presetId);
98
+ }
99
+
74
100
  // update models with presets IDs
75
- const models = _.map(config.models, (model) => {
76
- const presetIdsForModel = presetsIdsByModel[model.name];
101
+ return models.map((model) => {
102
+ let presetIdsForModel: string[] | undefined;
103
+ if ('srcType' in model && 'srcProjectId' in model) {
104
+ // "as" can be removed in typescript 4.3.5 and above
105
+ const srcType = (model as ModelWithSource).srcType;
106
+ const srcProjectId = (model as ModelWithSource).srcProjectId;
107
+ presetIdsForModel = presetsIdsByContentSourceAndModel[srcType]?.[srcProjectId]?.[model.name];
108
+ } else {
109
+ presetIdsForModel = presetsIdsByModel_deprecated[model.name];
110
+ }
77
111
  if (!presetIdsForModel) {
78
112
  return model;
79
113
  }
80
- return { ...model, presets: presetIdsForModel };
114
+ return {
115
+ ...model,
116
+ presets: presetIdsForModel
117
+ };
81
118
  });
82
-
83
- return {
84
- config: Object.assign({}, config, {
85
- models,
86
- presets
87
- }),
88
- errors
89
- };
90
119
  }
91
120
 
92
121
  function resolveThumbnailPath(thumbnail: string, dir: string) {
@@ -37,7 +37,7 @@ export function joiSchemasForModels(config: Config) {
37
37
  config.models,
38
38
  (modelSchemas: ModelSchemaMap, model: Model) => {
39
39
  let joiSchema: Joi.ObjectSchema;
40
- if (model.type !== 'image' && model.__metadata?.invalid) {
40
+ if (model.__metadata?.invalid) {
41
41
  // if root model is invalid, replace the label with "file" otherwise joi outputs "value" which is not descriptive
42
42
  let objectLabel = '{{#label}}';
43
43
  if (isDataModel(model) || isPageModel(model)) {
package/src/index.ts CHANGED
@@ -6,8 +6,27 @@ export * from './config/config-errors';
6
6
  export * from './analyzer/file-browser';
7
7
  export * from './utils';
8
8
  export * from './config/config-loader-static';
9
- export { RawConfigWithPaths, findStackbitConfigFile, isStackbitYamlFile, convertToYamlConfig } from './config/config-loader-utils';
10
- export { loadConfig, loadConfigFromDir, extendConfig, ConfigLoaderOptions, ConfigLoaderResult, ConfigLoaderResultWithStop } from './config/config-loader';
9
+ export { loadPresets, getPresetDirs, extendModelsWithPresetsIds } from './config/presets-loader';
10
+ export {
11
+ RawConfigWithPaths,
12
+ StopConfigWatch,
13
+ findStackbitConfigFile,
14
+ isStackbitYamlFile,
15
+ convertToYamlConfig,
16
+ getYamlModelDirs,
17
+ loadYamlModelsFromFiles,
18
+ mergeConfigModelsWithModelsFromFiles
19
+ } from './config/config-loader-utils';
20
+ export {
21
+ loadConfig,
22
+ LoadConfigResult,
23
+ loadConfigWithModelsPresetsAndValidate,
24
+ ConfigWithModelsPresetsResult,
25
+ loadConfigFromDir,
26
+ RawConfigLoaderResult,
27
+ mergeConfigModelsWithExternalModels
28
+ } from './config/config-loader';
29
+ export { validateConfig } from './config/config-validator';
11
30
  export { writeConfig, WriteConfigOptions } from './config/config-writer';
12
31
  export { loadContent, ContentItem, ContentLoaderOptions, ContentLoaderResult } from './content/content-loader';
13
32
  export { matchSSG, SSGMatcherOptions, SSGMatchResult } from './analyzer/ssg-matcher';
@@ -1,16 +1,4 @@
1
- type UnionKeys<U> = U extends any ? keyof U : never;
2
-
3
- type ForbiddenPropertiesOfUnionMember<T, Union, Keys extends string = UnionKeys<Union> extends string ? UnionKeys<Union> : never> = {
4
- [k in Exclude<Keys, keyof T>]?: undefined;
5
- };
6
-
7
- type StricterUnionMember<T, Union, Keys extends string = UnionKeys<Union> extends string ? UnionKeys<Union> : never> = T &
8
- ForbiddenPropertiesOfUnionMember<T, Union, Keys>;
9
-
10
- export type StricterUnion<Union, Union2 = Union> = Union2 extends any ? StricterUnionMember<Union2, Union> : never;
11
-
12
- export type Logger = Pick<Console, 'debug' | 'info' | 'warn' | 'error'>;
13
-
1
+ export { Logger } from '@stackbit/types';
14
2
  export * from './model-utils';
15
3
  export * from './model-matcher';
16
4
  export * from './model-extender';
@@ -95,7 +95,7 @@ function extendModel<T extends Model | YamlModel>(
95
95
  copyIfNotSet(extendedSuperModel, 'singleInstance', model, 'singleInstance');
96
96
  copyIfNotSet(extendedSuperModel, 'labelField', model, 'labelField');
97
97
  copyIfNotSet(extendedSuperModel, 'variantField', model, 'variantField');
98
- if (model.type !== 'image' && Array.isArray(extendedSuperModel.fieldGroups) && extendedSuperModel.fieldGroups.length > 0) {
98
+ if (Array.isArray(extendedSuperModel.fieldGroups) && extendedSuperModel.fieldGroups.length > 0) {
99
99
  _.set(model, 'fieldGroups', _.uniqBy(_.concat(extendedSuperModel.fieldGroups, _.get(model, 'fieldGroups', [])), 'name'));
100
100
  }
101
101
  let idx = 0;
@@ -1,8 +1,9 @@
1
1
  import _ from 'lodash';
2
-
3
2
  import { mapPromise, mapValuesPromise } from '@stackbit/utils';
3
+ import { Field, FieldList, FieldListItems, FieldModelProps, FieldObjectProps } from '@stackbit/types';
4
+
4
5
  import { getListFieldItems, isListDataModel, isListField, isObjectListItems, isModelField, isObjectField, isModelListItems } from './model-utils';
5
- import { Model, YamlModel, DataModel, Field, FieldList, FieldListItems, FieldModelProps, FieldObjectProps } from '../config/config-types';
6
+ import { Model, YamlModel, DataModel } from '../config/config-types';
6
7
 
7
8
  /**
8
9
  * This function invokes the `iteratee` function for every field of the `model`.
@@ -76,7 +77,7 @@ export function iterateModelFieldsRecursively(model: Model | YamlModel, iteratee
76
77
  }
77
78
  }
78
79
 
79
- export function mapModelFieldsRecursively(model: Model, iteratee: (field: Field, modelKeyPath: string[]) => Field) {
80
+ export function mapModelFieldsRecursively<T extends Model>(model: T, iteratee: (field: Field, modelKeyPath: string[]) => Field): T {
80
81
  function _mapField(field: Field, modelKeyPath: string[]): Field {
81
82
  if (!field) {
82
83
  return field;
@@ -92,7 +93,7 @@ export function mapModelFieldsRecursively(model: Model, iteratee: (field: Field,
92
93
  }
93
94
  }
94
95
 
95
- function _mapObjectField<T extends FieldObjectProps | Model>(field: T, modelKeyPath: string[]): T {
96
+ function _mapObjectField<Y extends FieldObjectProps | T>(field: Y, modelKeyPath: string[]): Y {
96
97
  const fields = field.fields;
97
98
  if (!fields) {
98
99
  return field;
@@ -104,7 +105,7 @@ export function mapModelFieldsRecursively(model: Model, iteratee: (field: Field,
104
105
  };
105
106
  }
106
107
 
107
- function _mapListField<T extends FieldList | (DataModel & { isList: true })>(field: T, modelKeyPath: string[]): T {
108
+ function _mapListField<Y extends FieldList | (T & { type: 'data'; isList: true })>(field: Y, modelKeyPath: string[]): Y {
108
109
  const items = field.items;
109
110
  if (!items || !isObjectListItems(items)) {
110
111
  return field;
@@ -7,14 +7,15 @@ import {
7
7
  DataModel,
8
8
  PageModel,
9
9
  ConfigModel,
10
- ImageModel,
11
10
  YamlModel,
12
11
  YamlObjectModel,
13
12
  YamlPageModel,
14
13
  YamlDataModel,
15
14
  YamlDataModelList,
16
15
  Field,
16
+ FieldSpecificProps,
17
17
  FieldEnum,
18
+ FieldEnumProps,
18
19
  FieldList,
19
20
  FieldListItems,
20
21
  FieldListModel,
@@ -26,7 +27,8 @@ import {
26
27
  FieldObjectProps,
27
28
  FieldReference,
28
29
  FieldReferenceProps,
29
- DataModelList
30
+ DataModelList,
31
+ FieldListProps
30
32
  } from '../config/config-types';
31
33
 
32
34
  export function getModelByName(models: Model[], modelName: string): Model | undefined {
@@ -53,10 +55,6 @@ export function isObjectModel(model: Model | YamlModel): model is ObjectModel |
53
55
  return model.type === 'object';
54
56
  }
55
57
 
56
- export function isImageModel(model: Model): model is ImageModel {
57
- return model.type === 'image';
58
- }
59
-
60
58
  export function isSingleInstanceModel(model: Model | YamlModel): boolean {
61
59
  if (model.type === 'config') {
62
60
  return true;
@@ -68,24 +66,24 @@ export function isSingleInstanceModel(model: Model | YamlModel): boolean {
68
66
  return false;
69
67
  }
70
68
 
71
- export function isObjectField(field: Field): field is FieldObject {
69
+ export function isObjectField(field: FieldSpecificProps): field is FieldObjectProps {
72
70
  return field.type === 'object';
73
71
  }
74
72
 
75
- export function isModelField(field: Field): field is FieldModel {
73
+ export function isModelField(field: FieldSpecificProps): field is FieldModelProps {
76
74
  return field.type === 'model';
77
75
  }
78
76
 
79
- export function isReferenceField(field: Field): field is FieldReference {
77
+ export function isReferenceField(field: FieldSpecificProps): field is FieldReferenceProps {
80
78
  return field.type === 'reference';
81
79
  }
82
80
 
83
- export function isCustomModelField(field: Field, modelsByName: Record<string, Model>) {
81
+ export function isCustomModelField(field: FieldSpecificProps, modelsByName: Record<string, Model>) {
84
82
  // custom model field types are deprecated
85
83
  return !FIELD_TYPES.includes(field.type) && _.has(modelsByName, field.type);
86
84
  }
87
85
 
88
- export function isEnumField(field: Field): field is FieldEnum {
86
+ export function isEnumField(field: FieldSpecificProps): field is FieldEnumProps {
89
87
  return field.type === 'enum';
90
88
  }
91
89
 
@@ -157,19 +155,21 @@ export function getListFieldItems(field: FieldList): FieldListItems {
157
155
  }
158
156
 
159
157
  export function normalizeListField(field: FieldList): FieldList {
160
- if (field.items?.type) {
158
+ // in older versions, the list 'items' were optional and when weren't
159
+ // specified a default list of strings was used.
160
+ if (_.get(field, 'items.type')) {
161
161
  return field;
162
162
  }
163
163
  return {
164
164
  ...field,
165
165
  items: {
166
- type: 'string',
167
- ...(field.items ?? {})
166
+ ...(field.items ?? {}),
167
+ type: 'string'
168
168
  }
169
- };
169
+ } as FieldList;
170
170
  }
171
171
 
172
- export function normalizeListFieldInPlace(field: FieldList): FieldList {
172
+ export function normalizeListFieldInPlace(field: FieldList | DataModelList): FieldList | DataModelList {
173
173
  // 'items.type' of list field default to 'string', set it explicitly
174
174
  if (!_.has(field, 'items.type')) {
175
175
  _.set(field, 'items.type', 'string');
@@ -177,6 +177,28 @@ export function normalizeListFieldInPlace(field: FieldList): FieldList {
177
177
  return field;
178
178
  }
179
179
 
180
+ export function getListItemsOrSelf(field: Field): Exclude<FieldSpecificProps, FieldListProps> {
181
+ if (isListField(field)) {
182
+ return normalizeListField(field).items;
183
+ }
184
+ return field;
185
+ }
186
+
187
+ export function mapListItemsPropsOrSelfSpecificProps(
188
+ field: Field,
189
+ func: <T extends Exclude<FieldSpecificProps, FieldListProps>>(listItemsPropsOrField: T) => T
190
+ ): Field {
191
+ if (isListField(field)) {
192
+ const listItems = normalizeListField(field).items;
193
+ const mappedListItems = func(listItems);
194
+ return {
195
+ ...field,
196
+ items: mappedListItems
197
+ } as FieldList;
198
+ }
199
+ return func(field);
200
+ }
201
+
180
202
  export function assignLabelFieldIfNeeded(modelOrField: Model | FieldObjectProps) {
181
203
  if (modelOrField.labelField) {
182
204
  return;