@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
@@ -22,7 +22,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
22
22
  return (mod && mod.__esModule) ? mod : { "default": mod };
23
23
  };
24
24
  Object.defineProperty(exports, "__esModule", { value: true });
25
- exports.loadConfigFromDir = exports.validateAndNormalizeConfig = exports.extendConfig = exports.loadConfig = void 0;
25
+ exports.mergeConfigModelsWithExternalModels = exports.loadConfigFromDir = exports.validateAndNormalizeConfig = exports.loadAndMergeModelsFromFiles = exports.loadConfig = exports.loadConfigWithModels = exports.loadConfigWithModelsPresetsAndValidate = void 0;
26
26
  const path_1 = __importDefault(require("path"));
27
27
  const fs_extra_1 = __importDefault(require("fs-extra"));
28
28
  const chokidar_1 = __importDefault(require("chokidar"));
@@ -35,29 +35,91 @@ const config_loader_esbuild_1 = require("./config-loader-esbuild");
35
35
  const utils_2 = require("../utils");
36
36
  const presets_loader_1 = require("./presets-loader");
37
37
  const config_loader_utils_1 = require("./config-loader-utils");
38
- async function loadConfig({ dirPath, modelsSource, stackbitConfigESBuildOutDir, watchCallback, logger }) {
39
- let wrappedCallback;
40
- if (watchCallback) {
41
- wrappedCallback = async (rawConfigResult) => {
42
- const configLoaderResult = await processConfigLoaderResult({ rawConfigResult, dirPath, modelsSource });
43
- watchCallback(configLoaderResult);
38
+ async function loadConfigWithModelsPresetsAndValidate({ dirPath, modelsSource, stackbitConfigESBuildOutDir, watchCallback, logger }) {
39
+ const configResult = await loadConfigWithModels({
40
+ dirPath,
41
+ stackbitConfigESBuildOutDir,
42
+ watchCallback: watchCallback
43
+ ? async (configResult) => {
44
+ const configLoaderResult = await processConfigLoaderResult({ configResult, dirPath, modelsSource });
45
+ watchCallback(configLoaderResult);
46
+ }
47
+ : undefined,
48
+ logger
49
+ });
50
+ const configLoaderResult = await processConfigLoaderResult({ configResult, dirPath, modelsSource });
51
+ return {
52
+ ...configLoaderResult,
53
+ stop: configResult.stop,
54
+ reload: configResult.reload
55
+ };
56
+ }
57
+ exports.loadConfigWithModelsPresetsAndValidate = loadConfigWithModelsPresetsAndValidate;
58
+ async function loadConfigWithModels({ dirPath, stackbitConfigESBuildOutDir, watchCallback, logger }) {
59
+ const wrapConfigResult = async (configResult) => {
60
+ if (!configResult.config) {
61
+ return {
62
+ config: null,
63
+ errors: configResult.errors
64
+ };
65
+ }
66
+ return await loadAndMergeModelsFromFiles(configResult.config);
67
+ };
68
+ const rawConfigResult = await loadConfig({
69
+ dirPath,
70
+ stackbitConfigESBuildOutDir,
71
+ logger,
72
+ watchCallback: watchCallback
73
+ ? async (configResult) => {
74
+ const wrappedResult = await wrapConfigResult(configResult);
75
+ watchCallback(wrappedResult);
76
+ }
77
+ : undefined
78
+ });
79
+ const wrappedResult = await wrapConfigResult(rawConfigResult);
80
+ return {
81
+ ...wrappedResult,
82
+ stop: rawConfigResult.stop,
83
+ reload: rawConfigResult.reload
84
+ };
85
+ }
86
+ exports.loadConfigWithModels = loadConfigWithModels;
87
+ async function loadConfig({ dirPath, stackbitConfigESBuildOutDir, watchCallback, logger }) {
88
+ const normalizeConfigResult = (rawConfigResult) => {
89
+ if (!rawConfigResult.config) {
90
+ return {
91
+ config: null,
92
+ errors: [rawConfigResult.error]
93
+ };
94
+ }
95
+ // TODO: validate config base properties after normalizing and return validation errors
96
+ const config = normalizeConfig(rawConfigResult.config);
97
+ return {
98
+ config: config,
99
+ errors: []
44
100
  };
45
- }
101
+ };
46
102
  const rawConfigResult = await loadConfigFromDir({
47
103
  dirPath,
48
104
  stackbitConfigESBuildOutDir,
49
- watchCallback: wrappedCallback,
105
+ watchCallback: watchCallback
106
+ ? async (rawConfigResult) => {
107
+ const normalizedResult = await normalizeConfigResult(rawConfigResult);
108
+ watchCallback(normalizedResult);
109
+ }
110
+ : undefined,
50
111
  logger
51
112
  });
52
- const configLoaderResult = await processConfigLoaderResult({ rawConfigResult, dirPath, modelsSource });
53
- return Object.assign(configLoaderResult, {
113
+ const normalizedResult = await normalizeConfigResult(rawConfigResult);
114
+ return {
115
+ ...normalizedResult,
54
116
  stop: rawConfigResult.stop,
55
117
  reload: rawConfigResult.reload
56
- });
118
+ };
57
119
  }
58
120
  exports.loadConfig = loadConfig;
59
- async function processConfigLoaderResult({ rawConfigResult, dirPath, modelsSource }) {
60
- const { config, errors: configLoadErrors } = rawConfigResult;
121
+ async function processConfigLoaderResult({ configResult, dirPath, modelsSource }) {
122
+ const { config, errors: configLoadErrors } = configResult;
61
123
  if (!config) {
62
124
  return {
63
125
  valid: false,
@@ -66,76 +128,95 @@ async function processConfigLoaderResult({ rawConfigResult, dirPath, modelsSourc
66
128
  };
67
129
  }
68
130
  const { models: externalModels, errors: externalModelsLoadErrors } = await loadModelsFromExternalSource(config, dirPath, modelsSource);
69
- const extendedConfig = await extendConfig({ config, externalModels });
70
- return {
71
- valid: extendedConfig.valid,
72
- config: extendedConfig.config,
73
- errors: [...configLoadErrors, ...externalModelsLoadErrors, ...extendedConfig.errors]
131
+ const mergedModels = mergeConfigModelsWithExternalModels({ configModels: config.models, externalModels });
132
+ const mergedConfig = {
133
+ ...config,
134
+ models: mergedModels
135
+ };
136
+ const normalizedResult = validateAndNormalizeConfig(mergedConfig);
137
+ const presetsResult = await presets_loader_1.loadPresets({ config: normalizedResult.config });
138
+ const modelsWithPresetIds = presets_loader_1.extendModelsWithPresetsIds({
139
+ models: normalizedResult.config.models,
140
+ presets: presetsResult.presets
141
+ });
142
+ const configWithPresets = {
143
+ ...normalizedResult.config,
144
+ models: modelsWithPresetIds,
145
+ presets: presetsResult.presets
74
146
  };
75
- }
76
- async function extendConfig({ config, externalModels }) {
77
- const normalizedResult = validateAndNormalizeConfig(config, externalModels);
78
- const presetsResult = await presets_loader_1.loadPresets(config.dirPath, normalizedResult.config);
79
147
  return {
80
148
  valid: normalizedResult.valid,
81
- config: presetsResult.config,
82
- errors: [...normalizedResult.errors, ...presetsResult.errors]
149
+ config: configWithPresets,
150
+ errors: [...configLoadErrors, ...externalModelsLoadErrors, ...normalizedResult.errors, ...presetsResult.errors]
83
151
  };
84
152
  }
85
- exports.extendConfig = extendConfig;
86
- function validateAndNormalizeConfig(config, externalModels) {
87
- // extend config models having the "extends" property
88
- // this must be done before any validation as some properties like
89
- // the labelField will not work when validating models without extending them first
90
- const { models: extendedModels, errors: extendModelErrors } = utils_2.extendModelMap(config.models);
153
+ async function loadAndMergeModelsFromFiles(config) {
154
+ const { models: modelsFromFiles, errors: fileModelsErrors } = await config_loader_utils_1.loadYamlModelsFromFiles(config);
155
+ const { models: mergedModels, errors: mergeModelErrors } = config_loader_utils_1.mergeConfigModelsWithModelsFromFiles(config.models, modelsFromFiles);
91
156
  const extendedConfig = {
92
157
  ...config,
93
- models: extendedModels
158
+ models: mergedModels
159
+ };
160
+ return {
161
+ config: extendedConfig,
162
+ errors: [...fileModelsErrors, ...mergeModelErrors]
94
163
  };
95
- const { config: mergedConfig, errors: externalModelsMergeErrors } = mergeConfigWithExternalModels(extendedConfig, externalModels);
164
+ }
165
+ exports.loadAndMergeModelsFromFiles = loadAndMergeModelsFromFiles;
166
+ function validateAndNormalizeConfig(config) {
96
167
  // validate the "contentModels" and extend config models with "contentModels"
97
168
  // this must be done before main config validation to make it independent of "contentModels".
98
- const { value: configWithContentModels, errors: contentModelsErrors } = validateAndExtendContentModels(mergedConfig);
169
+ const { config: configWithContentModels, errors: contentModelsErrors } = validateAndExtendContentModels(config);
99
170
  // normalize config - backward compatibility updates, adding extra fields like "markdown_content", "type" and "layout",
100
171
  // and setting other default values.
101
- const normalizedConfig = normalizeConfig(configWithContentModels);
172
+ const configWithNormalizedModels = normalizeModels(configWithContentModels);
102
173
  // validate config
103
- const { value: validatedConfig, errors: validationErrors } = config_validator_1.validateConfig(normalizedConfig);
104
- const errors = [...extendModelErrors, ...externalModelsMergeErrors, ...contentModelsErrors, ...validationErrors];
174
+ const { config: validatedConfig, errors: validationErrors } = config_validator_1.validateConfig(configWithNormalizedModels);
175
+ const errors = [...contentModelsErrors, ...validationErrors];
105
176
  return normalizeValidationResult({
106
177
  valid: lodash_1.default.isEmpty(errors),
107
- value: validatedConfig,
178
+ config: validatedConfig,
108
179
  errors: errors
109
180
  });
110
181
  }
111
182
  exports.validateAndNormalizeConfig = validateAndNormalizeConfig;
112
183
  async function loadConfigFromDir({ dirPath, stackbitConfigESBuildOutDir, watchCallback, logger }) {
184
+ function wrapResult(result, configFilePath) {
185
+ if (result.error) {
186
+ return {
187
+ config: null,
188
+ error: result.error
189
+ };
190
+ }
191
+ else {
192
+ return {
193
+ config: {
194
+ ...result.config,
195
+ dirPath: dirPath,
196
+ filePath: configFilePath
197
+ },
198
+ error: null
199
+ };
200
+ }
201
+ }
113
202
  // try to load stackbit config from YAML files
114
203
  try {
115
204
  const stackbitYamlPath = await utils_1.getFirstExistingFile(config_loader_utils_1.STACKBIT_CONFIG_YAML_FILES, dirPath);
116
205
  if (stackbitYamlPath) {
117
- const { config, errors } = await config_loader_utils_1.loadConfigWithModelsFromDir(dirPath);
206
+ logger === null || logger === void 0 ? void 0 : logger.debug(`loading Stackbit configuration from ${stackbitYamlPath}`);
207
+ const result = await config_loader_utils_1.loadStackbitYamlFromDir(dirPath);
118
208
  let close = async () => void 0;
119
209
  let reload = () => void 0;
120
210
  let stopped = false;
121
211
  if (watchCallback) {
122
- const watcher = chokidar_1.default.watch([...config_loader_utils_1.STACKBIT_CONFIG_YAML_FILES, '.stackbit'], {
212
+ const watcher = chokidar_1.default.watch([...config_loader_utils_1.STACKBIT_CONFIG_YAML_FILES], {
123
213
  cwd: dirPath,
124
214
  persistent: true,
125
215
  ignoreInitial: true
126
216
  });
127
217
  const throttledFileChange = lodash_1.default.throttle(async () => {
128
- const { config, errors } = await config_loader_utils_1.loadConfigWithModelsFromDir(dirPath);
129
- watchCallback({
130
- config: config
131
- ? {
132
- ...config,
133
- dirPath: dirPath,
134
- filePath: stackbitYamlPath
135
- }
136
- : undefined,
137
- errors
138
- });
218
+ const result = await config_loader_utils_1.loadStackbitYamlFromDir(dirPath);
219
+ watchCallback(wrapResult(result, stackbitYamlPath));
139
220
  }, 1000);
140
221
  const handleFileChange = (path) => {
141
222
  logger === null || logger === void 0 ? void 0 : logger.debug(`identified change in stackbit config file: ${path}, reloading config...`);
@@ -148,7 +229,8 @@ async function loadConfigFromDir({ dirPath, stackbitConfigESBuildOutDir, watchCa
148
229
  watcher.on('unlinkDir', handleFileChange);
149
230
  watcher.on('error', (error) => {
150
231
  watchCallback({
151
- errors: [new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })]
232
+ config: null,
233
+ error: new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })
152
234
  });
153
235
  });
154
236
  close = async () => {
@@ -164,45 +246,23 @@ async function loadConfigFromDir({ dirPath, stackbitConfigESBuildOutDir, watchCa
164
246
  };
165
247
  }
166
248
  return {
167
- config: config
168
- ? {
169
- ...config,
170
- dirPath: dirPath,
171
- filePath: stackbitYamlPath
172
- }
173
- : undefined,
249
+ ...wrapResult(result, stackbitYamlPath),
174
250
  stop: close,
175
- reload: reload,
176
- errors: errors
251
+ reload: reload
177
252
  };
178
253
  }
179
254
  }
180
255
  catch (error) {
181
256
  return {
182
- errors: [new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })]
257
+ config: null,
258
+ error: new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })
183
259
  };
184
260
  }
185
- function wrapResult(result, configFilePath) {
186
- if (result.error) {
187
- return {
188
- errors: [result.error]
189
- };
190
- }
191
- else {
192
- return {
193
- config: {
194
- ...result.config,
195
- dirPath: dirPath,
196
- filePath: configFilePath
197
- },
198
- errors: []
199
- };
200
- }
201
- }
202
261
  // try to load stackbit config from JavaScript files
203
262
  try {
204
263
  const configFilePath = await utils_1.getFirstExistingFile(config_loader_utils_1.STACKBIT_CONFIG_JS_FILES, dirPath);
205
264
  if (configFilePath) {
265
+ logger === null || logger === void 0 ? void 0 : logger.debug(`loading Stackbit configuration from: ${configFilePath}`);
206
266
  const configResult = await config_loader_esbuild_1.loadStackbitConfigFromJs({
207
267
  configPath: configFilePath,
208
268
  outDir: stackbitConfigESBuildOutDir !== null && stackbitConfigESBuildOutDir !== void 0 ? stackbitConfigESBuildOutDir : '.stackbit/cache',
@@ -219,11 +279,13 @@ async function loadConfigFromDir({ dirPath, stackbitConfigESBuildOutDir, watchCa
219
279
  }
220
280
  catch (error) {
221
281
  return {
222
- errors: [new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })]
282
+ config: null,
283
+ error: new config_errors_1.ConfigLoadError(`Error loading Stackbit configuration: ${error.message}`, { originalError: error })
223
284
  };
224
285
  }
225
286
  return {
226
- errors: [new config_errors_1.ConfigLoadError(config_errors_1.STACKBIT_CONFIG_NOT_FOUND)]
287
+ config: null,
288
+ error: new config_errors_1.ConfigLoadError(config_errors_1.STACKBIT_CONFIG_NOT_FOUND)
227
289
  };
228
290
  }
229
291
  exports.loadConfigFromDir = loadConfigFromDir;
@@ -285,34 +347,27 @@ async function loadConfigFromDotStackbit(dirPath) {
285
347
  }
286
348
  return lodash_1.default.isEmpty(config) ? null : config;
287
349
  }
288
- function mergeConfigWithExternalModels(config, externalModels) {
350
+ function mergeConfigModelsWithExternalModels({ configModels, externalModels }) {
289
351
  var _a;
290
- if (!externalModels || externalModels.length === 0) {
291
- return {
292
- config,
293
- errors: []
294
- };
352
+ if (externalModels.length === 0) {
353
+ return configModels;
295
354
  }
296
- const stackbitModels = (_a = config === null || config === void 0 ? void 0 : config.models) !== null && _a !== void 0 ? _a : {};
297
- const errors = [];
298
- const models = lodash_1.default.reduce(externalModels, (modelMap, externalModel) => {
299
- const { name, ...rest } = externalModel;
300
- return Object.assign(modelMap, { [name]: rest });
301
- }, {});
302
- lodash_1.default.forEach(stackbitModels, (stackbitModel, modelName) => {
303
- var _a, _b;
304
- let externalModel = models[modelName];
355
+ if (configModels.length === 0) {
356
+ return externalModels;
357
+ }
358
+ const mergedModelsByName = lodash_1.default.keyBy(externalModels, 'name');
359
+ for (const configModel of configModels) {
360
+ const externalModel = mergedModelsByName[configModel.name];
305
361
  if (!externalModel) {
306
- return;
362
+ continue;
363
+ }
364
+ const modelType = configModel.type ? (configModel.type === 'config' ? 'data' : configModel.type) : (_a = externalModel.type) !== null && _a !== void 0 ? _a : 'object';
365
+ let mergedModel = Object.assign({}, externalModel, lodash_1.default.pick(configModel, ['__metadata', 'urlPath', 'label', 'description', 'thumbnail', 'singleInstance', 'readOnly', 'labelField', 'fieldGroups']), { type: modelType });
366
+ if (mergedModel.type === 'page' && !mergedModel.urlPath) {
367
+ mergedModel.urlPath = '/{slug}';
307
368
  }
308
- const modelType = stackbitModel.type ? (stackbitModel.type === 'config' ? 'data' : stackbitModel.type) : (_a = externalModel.type) !== null && _a !== void 0 ? _a : 'object';
309
- const urlPath = modelType === 'page' ? (_b = stackbitModel === null || stackbitModel === void 0 ? void 0 : stackbitModel.urlPath) !== null && _b !== void 0 ? _b : '/{slug}' : null;
310
- externalModel = Object.assign({}, externalModel, lodash_1.default.pick(stackbitModel, ['__metadata', 'label', 'description', 'thumbnail', 'singleInstance', 'readOnly', 'labelField', 'fieldGroups']), utils_1.omitByNil({
311
- type: modelType,
312
- urlPath
313
- }));
314
- externalModel = utils_2.mapModelFieldsRecursively(externalModel, (externalField, modelKeyPath) => {
315
- const stackbitField = utils_2.getModelFieldForModelKeyPath(stackbitModel, modelKeyPath);
369
+ mergedModel = utils_2.mapModelFieldsRecursively(mergedModel, (externalField, modelKeyPath) => {
370
+ const stackbitField = utils_2.getModelFieldForModelKeyPath(configModel, modelKeyPath);
316
371
  if (!stackbitField) {
317
372
  return externalField;
318
373
  }
@@ -332,40 +387,52 @@ function mergeConfigWithExternalModels(config, externalModels) {
332
387
  else if (externalField.type === 'object') {
333
388
  override = lodash_1.default.pick(stackbitField, ['labelField', 'thumbnail', 'fieldGroups']);
334
389
  }
390
+ else if (externalField.type === 'reference' || externalField.type === 'model') {
391
+ override = lodash_1.default.pick(stackbitField, ['models']);
392
+ }
335
393
  return Object.assign({}, externalField, lodash_1.default.pick(stackbitField, ['label', 'description', 'required', 'default', 'group', 'const', 'hidden', 'readOnly', 'controlType']), override);
336
394
  });
337
- models[modelName] = externalModel;
338
- });
395
+ mergedModelsByName[configModel.name] = mergedModel;
396
+ }
397
+ return Object.values(mergedModelsByName);
398
+ }
399
+ exports.mergeConfigModelsWithExternalModels = mergeConfigModelsWithExternalModels;
400
+ function normalizeConfig(rawConfig) {
401
+ const stackbitVersion = String(lodash_1.default.get(rawConfig, 'stackbitVersion', config_loader_utils_1.LATEST_STACKBIT_VERSION));
402
+ const ver = semver_1.default.coerce(stackbitVersion);
403
+ const isGTEStackbitYamlV5 = ver ? semver_1.default.satisfies(ver, '>=0.5.0') : false;
404
+ const { logicFields, models: modelMap, ...restConfig } = rawConfig;
405
+ // in stackbit.yaml 'models' are defined as object where keys are the model names,
406
+ // convert 'models' to array of objects and set their 'name' property to the model name
407
+ const models = lodash_1.default.reduce(modelMap, (accum, model, modelName) => accum.concat(Object.assign({ name: modelName }, model)), []);
339
408
  return {
340
- config: {
341
- ...config,
342
- models: models
343
- },
344
- errors: errors
409
+ ...restConfig,
410
+ stackbitVersion: stackbitVersion,
411
+ models: models,
412
+ noEncodeFields: logicFields,
413
+ hcrHandled: !stackbitVersion || lodash_1.default.get(rawConfig, 'customContentReload', lodash_1.default.get(rawConfig, 'hcrHandled', !isGTEStackbitYamlV5)),
414
+ internalStackbitRunnerOptions: getInternalStackbitRunnerOptions(rawConfig)
345
415
  };
346
416
  }
347
- function normalizeConfig(config) {
348
- const pageLayoutKey = lodash_1.default.get(config, 'pageLayoutKey', 'layout');
349
- const objectTypeKey = lodash_1.default.get(config, 'objectTypeKey', 'type');
350
- const stackbitYamlVersion = String(lodash_1.default.get(config, 'stackbitVersion', ''));
417
+ function normalizeModels(config) {
418
+ var _a, _b, _c;
419
+ const pageLayoutKey = (_a = config.pageLayoutKey) !== null && _a !== void 0 ? _a : 'layout';
420
+ const objectTypeKey = (_b = config.objectTypeKey) !== null && _b !== void 0 ? _b : 'type';
421
+ const stackbitYamlVersion = String((_c = config.stackbitVersion) !== null && _c !== void 0 ? _c : '');
351
422
  const ver = semver_1.default.coerce(stackbitYamlVersion);
352
423
  const isStackbitYamlV2 = ver ? semver_1.default.satisfies(ver, '<0.3.0') : false;
353
- const isStackbitYamlV5 = ver ? semver_1.default.satisfies(ver, '>=0.5.0') : false;
354
- const models = (config === null || config === void 0 ? void 0 : config.models) || {};
424
+ const models = config.models;
425
+ const modelsByName = lodash_1.default.keyBy(models, 'name');
355
426
  const gitCMS = isGitCMS(config);
356
- utils_1.rename(config, 'logicFields', 'noEncodeFields');
357
- config.hcrHandled = !stackbitYamlVersion || lodash_1.default.get(config, 'customContentReload', lodash_1.default.get(config, 'hcrHandled', !isStackbitYamlV5));
358
- config.internalStackbitRunnerOptions = getInternalStackbitRunnerOptions(config);
359
- lodash_1.default.forEach(models, (model, modelName) => {
360
- if (!model) {
361
- return;
362
- }
427
+ const mappedModels = models.map((model) => {
428
+ // create shallow copy of the model to prevent mutation of original models
429
+ model = { ...model };
363
430
  if (!lodash_1.default.has(model, 'type')) {
364
431
  model.type = 'object';
365
432
  }
366
433
  // add model label if not set
367
434
  if (!lodash_1.default.has(model, 'label')) {
368
- model.label = lodash_1.default.startCase(modelName);
435
+ model.label = lodash_1.default.startCase(model.name);
369
436
  }
370
437
  if (lodash_1.default.has(model, 'fields') && !Array.isArray(model.fields)) {
371
438
  model.fields = [];
@@ -387,17 +454,15 @@ function normalizeConfig(config) {
387
454
  // The content validator should always assume these fields.
388
455
  // And when new objects created from UI, it should add these fields automatically.
389
456
  if (utils_2.isPageModel(model)) {
390
- addLayoutFieldToPageModel(model, pageLayoutKey, modelName);
457
+ addLayoutFieldToPageModel(model, pageLayoutKey, model.name);
391
458
  }
392
459
  else if (utils_2.isDataModel(model) && !utils_2.isListDataModel(model)) {
393
- addObjectTypeKeyField(model, objectTypeKey, modelName);
460
+ addObjectTypeKeyField(model, objectTypeKey, model.name);
394
461
  }
395
462
  }
396
463
  if (utils_2.isListDataModel(model)) {
397
464
  // 'items.type' of list model defaults to 'string', set it explicitly
398
- if (!lodash_1.default.has(model, 'items.type')) {
399
- lodash_1.default.set(model, 'items.type', 'string');
400
- }
465
+ utils_2.normalizeListFieldInPlace(model);
401
466
  if (utils_2.isObjectListItems(model.items)) {
402
467
  utils_2.assignLabelFieldIfNeeded(model.items);
403
468
  }
@@ -405,50 +470,65 @@ function normalizeConfig(config) {
405
470
  else if (!lodash_1.default.has(model, 'labelField')) {
406
471
  utils_2.assignLabelFieldIfNeeded(model);
407
472
  }
408
- utils_2.iterateModelFieldsRecursively(model, (field) => {
473
+ return utils_2.mapModelFieldsRecursively(model, (field) => {
474
+ // create shallow copy of the field to prevent mutation of original field
475
+ field = { ...field };
409
476
  // add field label if label is not set
410
477
  if (!lodash_1.default.has(field, 'label')) {
411
478
  field.label = lodash_1.default.startCase(field.name);
412
479
  }
413
- if (utils_2.isListField(field)) {
414
- field = utils_2.normalizeListFieldInPlace(field);
415
- field = field.items;
416
- }
417
- if (utils_2.isObjectField(field)) {
418
- utils_2.assignLabelFieldIfNeeded(field);
419
- }
420
- else if (utils_2.isCustomModelField(field, models)) {
421
- // stackbit v0.2.0 compatibility
422
- // convert the old custom model field type: { type: 'action' }
423
- // to the new 'model' field type: { type: 'model', models: ['action'] }
424
- field.models = [field.type];
425
- field.type = 'model';
426
- }
427
- else if (field.type === 'models') {
428
- // stackbit v0.2.0 compatibility
429
- // convert the old 'models' field type: { type: 'models', models: ['link', 'button'] }
430
- // to the new 'model' field type: { type: 'model', models: ['link', 'button'] }
431
- field.type = 'model';
432
- field.models = lodash_1.default.get(field, 'models', []);
433
- }
434
- else if (field.type === 'model' && lodash_1.default.has(field, 'model')) {
435
- // stackbit v0.2.0 compatibility
436
- // convert the old 'model' field type: { type: 'model', model: 'link' }
437
- // to the new 'model' field type: { type: 'model', models: ['link'] }
438
- field.models = [field.model];
439
- delete field.model;
440
- }
441
- if (isStackbitYamlV2) {
442
- // in stackbit.yaml v0.2.x, the 'reference' field was what we have today as 'model' field:
443
- if (utils_2.isReferenceField(field)) {
444
- field = field;
445
- field.type = 'model';
446
- field.models = lodash_1.default.get(field, 'models', []);
480
+ return utils_2.mapListItemsPropsOrSelfSpecificProps(field, (fieldSpecificProps) => {
481
+ if (utils_2.isObjectField(fieldSpecificProps)) {
482
+ utils_2.assignLabelFieldIfNeeded(fieldSpecificProps);
447
483
  }
448
- }
484
+ else if (utils_2.isCustomModelField(fieldSpecificProps, modelsByName)) {
485
+ // stackbit v0.2.0 compatibility
486
+ // convert the old custom model field type: { type: 'action' }
487
+ // to the new 'model' field type: { type: 'model', models: ['action'] }
488
+ fieldSpecificProps = {
489
+ ...fieldSpecificProps,
490
+ type: 'model',
491
+ models: [fieldSpecificProps.type]
492
+ };
493
+ }
494
+ else if (fieldSpecificProps.type === 'models') {
495
+ // stackbit v0.2.0 compatibility
496
+ // convert the old 'models' field type: { type: 'models', models: ['link', 'button'] }
497
+ // to the new 'model' field type: { type: 'model', models: ['link', 'button'] }
498
+ fieldSpecificProps = {
499
+ ...fieldSpecificProps,
500
+ type: 'model',
501
+ models: lodash_1.default.get(fieldSpecificProps, 'models', [])
502
+ };
503
+ }
504
+ else if (fieldSpecificProps.type === 'model' && lodash_1.default.has(fieldSpecificProps, 'model')) {
505
+ // stackbit v0.2.0 compatibility
506
+ // convert the old 'model' field type: { type: 'model', model: 'link' }
507
+ // to the new 'model' field type: { type: 'model', models: ['link'] }
508
+ const { model, ...rest } = fieldSpecificProps;
509
+ fieldSpecificProps = {
510
+ ...rest,
511
+ models: [model]
512
+ };
513
+ }
514
+ if (isStackbitYamlV2) {
515
+ // in stackbit.yaml v0.2.x, the 'reference' field was what we have today as 'model' field:
516
+ if (utils_2.isReferenceField(fieldSpecificProps)) {
517
+ fieldSpecificProps = {
518
+ ...fieldSpecificProps,
519
+ type: 'model',
520
+ models: lodash_1.default.get(fieldSpecificProps, 'models', [])
521
+ };
522
+ }
523
+ }
524
+ return fieldSpecificProps;
525
+ });
449
526
  });
450
527
  });
451
- return config;
528
+ return {
529
+ ...config,
530
+ models: mappedModels
531
+ };
452
532
  }
453
533
  function updatePageUrlPath(model) {
454
534
  // set default urlPath if not set
@@ -553,26 +633,23 @@ function addObjectTypeKeyField(model, objectTypeKey, modelName) {
553
633
  }
554
634
  /**
555
635
  * Returns model names referenced by polymorphic 'model' and 'reference' fields.
556
- * That is, fields that can hold objects of different types.
557
636
  *
558
637
  * @param field
559
638
  */
560
639
  function getReferencedModelNames(field) {
561
640
  var _a, _b;
562
- if (utils_2.isListField(field)) {
563
- field = utils_2.getListFieldItems(field);
564
- }
641
+ const fieldSpecificProps = utils_2.getListItemsOrSelf(field);
565
642
  // TODO: add type field to model fields inside container update/create object logic rather adding type to schema
566
643
  // 'object' models referenced by 'model' fields should have 'type' field
567
644
  // if these fields have than 1 model.
568
645
  // 'data' models referenced by 'reference' fields should always have 'type' field.
569
646
  let referencedModelNames = [];
570
- if (utils_2.isModelField(field) && ((_a = field.models) === null || _a === void 0 ? void 0 : _a.length) > 1) {
571
- const modelNames = field.models;
647
+ if (utils_2.isModelField(fieldSpecificProps) && ((_a = fieldSpecificProps.models) === null || _a === void 0 ? void 0 : _a.length) > 1) {
648
+ const modelNames = fieldSpecificProps.models;
572
649
  referencedModelNames = lodash_1.default.union(referencedModelNames, modelNames);
573
650
  }
574
- else if (utils_2.isReferenceField(field) && ((_b = field.models) === null || _b === void 0 ? void 0 : _b.length) > 0) {
575
- const modelNames = field.models;
651
+ else if (utils_2.isReferenceField(fieldSpecificProps) && ((_b = fieldSpecificProps.models) === null || _b === void 0 ? void 0 : _b.length) > 0) {
652
+ const modelNames = fieldSpecificProps.models;
576
653
  referencedModelNames = lodash_1.default.union(referencedModelNames, modelNames);
577
654
  }
578
655
  return referencedModelNames;
@@ -580,46 +657,47 @@ function getReferencedModelNames(field) {
580
657
  function validateAndExtendContentModels(config) {
581
658
  var _a, _b;
582
659
  const contentModels = (_a = config.contentModels) !== null && _a !== void 0 ? _a : {};
583
- const models = (_b = config.models) !== null && _b !== void 0 ? _b : {};
660
+ const models = (_b = config.models) !== null && _b !== void 0 ? _b : [];
661
+ // external models already merged in mergeConfigModelsWithExternalModels function
584
662
  const externalModels = !isGitCMS(config);
585
663
  const emptyContentModels = lodash_1.default.isEmpty(contentModels);
586
664
  if (externalModels || emptyContentModels) {
587
665
  return {
588
- valid: true,
589
- value: config,
666
+ config: config,
590
667
  errors: []
591
668
  };
592
669
  }
593
670
  const validationResult = config_validator_1.validateContentModels(contentModels, models);
594
671
  if (lodash_1.default.isEmpty(models)) {
595
672
  return {
596
- valid: validationResult.valid,
597
- value: config,
673
+ config: config,
598
674
  errors: validationResult.errors
599
675
  };
600
676
  }
601
- const extendedModels = lodash_1.default.mapValues(models, (model, modelName) => {
602
- const contentModel = validationResult.value.contentModels[modelName];
677
+ const extendedModels = models.map((model) => {
678
+ const contentModel = validationResult.contentModels[model.name];
603
679
  if (!contentModel) {
604
680
  return model;
605
681
  }
606
682
  if (lodash_1.default.get(contentModel, '__metadata.invalid')) {
607
683
  return model;
608
684
  }
609
- if (contentModel.isPage && (!model.type || ['object', 'page'].includes(model.type))) {
685
+ const { isPage, newFilePath, ...restContentModel } = contentModel;
686
+ const { type, ...restModel } = model;
687
+ if (isPage && (!type || ['object', 'page'].includes(type))) {
610
688
  return {
611
689
  type: 'page',
612
- ...(contentModel.newFilePath ? { filePath: contentModel.newFilePath } : {}),
613
- ...lodash_1.default.omit(contentModel, ['isPage', 'newFilePath']),
614
- ...lodash_1.default.omit(model, 'type')
690
+ ...(newFilePath ? { filePath: newFilePath } : {}),
691
+ ...restContentModel,
692
+ ...restModel
615
693
  };
616
694
  }
617
- else if (!contentModel.isPage && (!model.type || ['object', 'data'].includes(model.type))) {
695
+ else if (!isPage && (!type || ['object', 'data'].includes(type))) {
618
696
  return {
619
697
  type: 'data',
620
- ...(contentModel.newFilePath ? { filePath: contentModel.newFilePath } : {}),
621
- ...lodash_1.default.omit(contentModel, ['isPage', 'newFilePath']),
622
- ...lodash_1.default.omit(model, 'type')
698
+ ...(newFilePath ? { filePath: newFilePath } : {}),
699
+ ...restContentModel,
700
+ ...restModel
623
701
  };
624
702
  }
625
703
  else {
@@ -627,8 +705,7 @@ function validateAndExtendContentModels(config) {
627
705
  }
628
706
  });
629
707
  return {
630
- valid: validationResult.valid,
631
- value: {
708
+ config: {
632
709
  ...config,
633
710
  models: extendedModels
634
711
  },
@@ -637,14 +714,13 @@ function validateAndExtendContentModels(config) {
637
714
  }
638
715
  function normalizeValidationResult(validationResult) {
639
716
  validationResult = filterAndOrderConfigFields(validationResult);
640
- convertModelGroupsToModelList(validationResult);
641
- return convertModelsToArray(validationResult);
717
+ return convertModelGroupsToModelListInPlace(validationResult);
642
718
  }
643
719
  function filterAndOrderConfigFields(validationResult) {
644
720
  // TODO: check if we can move filtering and sorting to Joi
645
721
  return {
646
722
  ...validationResult,
647
- value: lodash_1.default.pick(validationResult.value, [
723
+ config: lodash_1.default.pick(validationResult.config, [
648
724
  'stackbitVersion',
649
725
  'ssgName',
650
726
  'ssgVersion',
@@ -694,16 +770,16 @@ function filterAndOrderConfigFields(validationResult) {
694
770
  * Collects models groups and injects them into the `models` array of the
695
771
  * `reference` and `model` field types
696
772
  */
697
- function convertModelGroupsToModelList(validationResult) {
773
+ function convertModelGroupsToModelListInPlace(validationResult) {
698
774
  var _a, _b;
699
- const models = (_b = (_a = validationResult.value) === null || _a === void 0 ? void 0 : _a.models) !== null && _b !== void 0 ? _b : {};
700
- const groupMap = lodash_1.default.reduce(models, (groupMap, model, modelName) => {
775
+ const models = (_b = (_a = validationResult.config) === null || _a === void 0 ? void 0 : _a.models) !== null && _b !== void 0 ? _b : [];
776
+ const groupMap = lodash_1.default.reduce(models, (groupMap, model) => {
701
777
  if (!model.groups) {
702
778
  return groupMap;
703
779
  }
704
780
  const key = (model === null || model === void 0 ? void 0 : model.type) === 'object' ? 'objectModels' : 'documentModels';
705
781
  lodash_1.default.forEach(model.groups, (groupName) => {
706
- utils_1.append(groupMap, [groupName, key], modelName);
782
+ utils_1.append(groupMap, [groupName, key], model.name);
707
783
  });
708
784
  delete model.groups;
709
785
  return groupMap;
@@ -714,78 +790,31 @@ function convertModelGroupsToModelList(validationResult) {
714
790
  lodash_1.default.set(group, key, lodash_1.default.uniq(modelGroup));
715
791
  });
716
792
  });
717
- lodash_1.default.forEach(models, (model) => {
718
- utils_2.iterateModelFieldsRecursively(model, (field) => {
719
- if (utils_2.isListField(field)) {
720
- field = field.items;
721
- }
722
- if (field.groups) {
723
- let key = null;
724
- if (utils_2.isModelField(field)) {
725
- key = 'objectModels';
726
- }
727
- else if (utils_2.isReferenceField(field)) {
728
- key = 'documentModels';
793
+ const mappedModels = models.map((model) => {
794
+ return utils_2.mapModelFieldsRecursively(model, (field) => {
795
+ return utils_2.mapListItemsPropsOrSelfSpecificProps(field, (fieldSpecificProps) => {
796
+ if (!utils_2.isModelField(fieldSpecificProps) && !utils_2.isReferenceField(fieldSpecificProps)) {
797
+ return fieldSpecificProps;
729
798
  }
730
- if (key) {
731
- field.models = lodash_1.default.reduce(field.groups, (modelNames, groupName) => {
732
- const objectModelNames = lodash_1.default.get(groupMap, [groupName, key], []);
733
- return lodash_1.default.uniq(modelNames.concat(objectModelNames));
734
- }, field.models || []);
735
- }
736
- delete field.groups;
737
- }
799
+ const { ...cloned } = fieldSpecificProps;
800
+ const key = utils_2.isModelField(fieldSpecificProps) ? 'objectModels' : 'documentModels';
801
+ const modelNames = lodash_1.default.reduce(cloned.groups, (modelNames, groupName) => {
802
+ const objectModelNames = lodash_1.default.get(groupMap, [groupName, key], []);
803
+ return lodash_1.default.uniq(modelNames.concat(objectModelNames));
804
+ }, fieldSpecificProps.models || []);
805
+ delete cloned.groups;
806
+ return Object.assign(cloned, { models: modelNames });
807
+ });
738
808
  });
739
809
  });
740
- }
741
- function convertModelsToArray(validationResult) {
742
- const config = validationResult.value;
743
- const { stackbitVersion = config_loader_utils_1.LATEST_STACKBIT_VERSION, models, ...rest } = config;
744
- // in stackbit.yaml 'models' are defined as object where keys are the model names,
745
- // convert 'models' to array of objects and set their 'name' property to the
746
- // model name
747
- const modelMap = models !== null && models !== void 0 ? models : {};
748
- const modelArray = lodash_1.default.map(modelMap, (yamlModel, modelName) => {
749
- return {
750
- name: modelName,
751
- ...yamlModel
752
- };
753
- });
754
- if (!isGitCMS(config)) {
755
- addImageModel(modelArray);
756
- }
757
- const convertedErrors = lodash_1.default.map(validationResult.errors, (error) => {
758
- if (error.fieldPath[0] === 'models' && typeof error.fieldPath[1] == 'string') {
759
- const modelName = error.fieldPath[1];
760
- const modelIndex = lodash_1.default.findIndex(modelArray, { name: modelName });
761
- const normFieldPath = error.fieldPath.slice();
762
- normFieldPath[1] = modelIndex;
763
- error.normFieldPath = normFieldPath;
764
- }
765
- return error;
766
- });
767
810
  return {
768
- valid: validationResult.valid,
811
+ ...validationResult,
769
812
  config: {
770
- stackbitVersion,
771
- ...rest,
772
- models: modelArray
773
- },
774
- errors: convertedErrors
813
+ ...validationResult.config,
814
+ models: mappedModels
815
+ }
775
816
  };
776
817
  }
777
- function addImageModel(models) {
778
- models.push({
779
- type: 'image',
780
- name: '__image_model',
781
- label: 'Image',
782
- labelField: 'title',
783
- fields: [
784
- { name: 'title', type: 'string' },
785
- { name: 'url', type: 'string' }
786
- ]
787
- });
788
- }
789
818
  function isGitCMS(config) {
790
819
  return !config.contentSources && (!config.cmsName || config.cmsName === 'git');
791
820
  }