@stackbit/sdk
Version:
247 lines • 11.3 kB
JavaScript
;
var __importDefault = (this && this.__importDefault) || function (mod) {
return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.mergeConfigModelsWithModelsFromFiles = exports.getYamlModelDirs = exports.loadYamlModelsFromFiles = exports.convertToYamlConfig = exports.isStackbitYamlFile = exports.findStackbitConfigFile = exports.loadConfigFromStackbitYaml = exports.loadStackbitYamlFromDir = exports.LATEST_STACKBIT_VERSION = exports.STACKBIT_CONFIG_FILES = exports.STACKBIT_CONFIG_JS_FILES = exports.STACKBIT_CONFIG_YAML_FILES = void 0;
const path_1 = __importDefault(require("path"));
const fs_extra_1 = __importDefault(require("fs-extra"));
const js_yaml_1 = __importDefault(require("js-yaml"));
const lodash_1 = __importDefault(require("lodash"));
const utils_1 = require("@stackbit/utils");
const config_errors_1 = require("./config-errors");
const utils_2 = require("../utils");
exports.STACKBIT_CONFIG_YAML_FILES = ['stackbit.yaml', 'stackbit.yml'];
exports.STACKBIT_CONFIG_JS_FILES = ['stackbit.config.js', 'stackbit.config.cjs', 'stackbit.config.mjs', 'stackbit.config.ts'];
exports.STACKBIT_CONFIG_FILES = [...exports.STACKBIT_CONFIG_YAML_FILES, ...exports.STACKBIT_CONFIG_JS_FILES];
exports.LATEST_STACKBIT_VERSION = '0.7.0';
async function loadStackbitYamlFromDir(dirPath) {
const stackbitYamlPath = await (0, utils_1.getFirstExistingFile)(exports.STACKBIT_CONFIG_YAML_FILES, dirPath);
if (!stackbitYamlPath) {
return {
config: null,
error: new config_errors_1.StackbitConfigNotFoundError()
};
}
return await loadConfigFromStackbitYaml(stackbitYamlPath);
}
exports.loadStackbitYamlFromDir = loadStackbitYamlFromDir;
async function loadConfigFromStackbitYaml(stackbitYamlPath) {
const stackbitYaml = await fs_extra_1.default.readFile(stackbitYamlPath, 'utf8');
const config = js_yaml_1.default.load(stackbitYaml, { schema: js_yaml_1.default.JSON_SCHEMA });
if (!config || typeof config !== 'object') {
const fileName = path_1.default.basename(stackbitYamlPath);
return {
config: null,
error: new config_errors_1.ConfigLoadError(`error parsing ${fileName}, ${config_errors_1.REFER_TO_STACKBIT_CONFIG_DOCS}`)
};
}
return {
config: config,
error: null
};
}
exports.loadConfigFromStackbitYaml = loadConfigFromStackbitYaml;
async function findStackbitConfigFile(dirs) {
for (const dir of dirs) {
for (const fileName of exports.STACKBIT_CONFIG_FILES) {
const filePath = path_1.default.resolve(dir, fileName);
if (await fs_extra_1.default.pathExists(filePath)) {
return filePath;
}
}
}
return null;
}
exports.findStackbitConfigFile = findStackbitConfigFile;
function isStackbitYamlFile(filePath) {
const pathObject = path_1.default.parse(filePath);
return pathObject.base === 'stackbit.yaml' || pathObject.dir.split(path_1.default.sep).includes('.stackbit');
}
exports.isStackbitYamlFile = isStackbitYamlFile;
function convertToYamlConfig({ config }) {
const yamlConfig = lodash_1.default.cloneDeep(lodash_1.default.omit(config, ['models', 'dirPath', 'filePath', 'presets']));
if (!lodash_1.default.isEmpty(config.models)) {
yamlConfig.models = lodash_1.default.reduce(config.models, (yamlModels, model) => {
const yamlModel = lodash_1.default.omit(model, ['name', '__metadata', 'presets']);
switch (yamlModel.type) {
case 'page':
if (!yamlModel.hideContent && yamlModel.fields) {
lodash_1.default.remove(yamlModel.fields, (field) => field.name === 'markdown_content');
}
if (yamlModel.fields) {
lodash_1.default.remove(yamlModel.fields, (field) => field.name === (config.pageLayoutKey || 'layout'));
}
yamlModels[model.name] = yamlModel;
break;
case 'data':
if (yamlModel.fields) {
lodash_1.default.remove(yamlModel.fields, (field) => field.name === (config.objectTypeKey || 'type'));
}
break;
case 'object':
case 'config':
yamlModels[model.name] = yamlModel;
break;
default: {
const _exhaustiveCheck = yamlModel;
return _exhaustiveCheck;
}
}
yamlModels[model.name] = yamlModel;
return yamlModels;
}, {});
}
return yamlConfig;
}
exports.convertToYamlConfig = convertToYamlConfig;
async function loadYamlModelsFromFiles(config) {
const dirPath = config.dirPath;
const modelDirs = getYamlModelDirs(config);
const modelFiles = await (0, utils_1.reducePromise)(modelDirs, async (modelFiles, modelDir) => {
const absModelsDir = path_1.default.join(dirPath, modelDir);
const dirExists = await fs_extra_1.default.pathExists(absModelsDir);
if (!dirExists) {
return modelFiles;
}
const files = await readYamlModelFilesFromDir(absModelsDir);
return modelFiles.concat(files.map((filePath) => path_1.default.join(modelDir, filePath)));
}, []);
const result = await (0, utils_1.reducePromise)(modelFiles, async (result, modelFile) => {
let model;
try {
model = await (0, utils_1.parseFile)(path_1.default.join(dirPath, modelFile));
}
catch (error) {
return {
modelMap: result.modelMap,
errors: result.errors.concat(new config_errors_1.ModelLoadError(`error parsing model, file: ${modelFile}`))
};
}
const modelName = model?.name;
if (!modelName) {
return {
modelMap: result.modelMap,
errors: result.errors.concat(new config_errors_1.ModelLoadError(`model does not have a name, file: ${modelFile}`))
};
}
result.modelMap[modelName] = {
__metadata: {
filePath: modelFile
},
...model
};
return result;
}, { modelMap: {}, errors: [] });
return {
models: Object.values(result.modelMap),
errors: result.errors
};
}
exports.loadYamlModelsFromFiles = loadYamlModelsFromFiles;
function getYamlModelDirs(config) {
const modelsSource = lodash_1.default.get(config, 'modelsSource', {});
const sourceType = lodash_1.default.get(modelsSource, 'type', 'files');
const defaultModelDirs = ['node_modules/@stackbit/components/models', '.stackbit/models'];
const modelDirs = lodash_1.default.get(modelsSource, 'modelDirs', defaultModelDirs);
return sourceType === 'files' ? lodash_1.default.castArray(modelDirs).map((modelDir) => lodash_1.default.trim(modelDir, '/')) : defaultModelDirs;
}
exports.getYamlModelDirs = getYamlModelDirs;
async function readYamlModelFilesFromDir(modelsDir) {
return await (0, utils_1.readDirRecursively)(modelsDir, {
filter: (filePath, stats) => {
if (stats.isDirectory()) {
return true;
}
const extension = path_1.default.extname(filePath).substring(1);
return stats.isFile() && ['yaml', 'yml'].includes(extension);
}
});
}
function mergeConfigModelsWithModelsFromFiles(configModels, modelsFromFiles) {
const configModelsByName = lodash_1.default.keyBy(configModels, 'name');
const mergedModelsFromFiles = modelsFromFiles.map((modelFromFile) => {
// resolve thumbnails of models loaded from files
const modelFilePath = modelFromFile.__metadata?.filePath;
modelFromFile = resolveThumbnailPathForModelOrObjectField(modelFromFile, modelFilePath);
modelFromFile = (0, utils_2.mapModelFieldsRecursively)(modelFromFile, (field) => {
if ((0, utils_2.isListField)(field)) {
field = (0, utils_2.normalizeListField)(field);
field = {
...field,
items: resolveThumbnailForEnumField(field.items, modelFilePath)
};
}
else {
field = resolveThumbnailForEnumField(field, modelFilePath);
}
return field;
});
const configModel = lodash_1.default.get(configModelsByName, modelFromFile.name);
if (!configModel) {
return modelFromFile;
}
return lodash_1.default.assign({}, modelFromFile, configModel, {
fields: lodash_1.default.unionBy(configModel?.fields ?? [], modelFromFile?.fields ?? [], 'name')
});
});
const mergedModels = lodash_1.default.unionBy(mergedModelsFromFiles, configModels, 'name');
// extend config models having the "extends" property
// this must be done before any validation as some properties like
// the labelField will not work when validating models without extending them first
return (0, utils_2.extendModelArray)(mergedModels);
}
exports.mergeConfigModelsWithModelsFromFiles = mergeConfigModelsWithModelsFromFiles;
function resolveThumbnailForEnumField(field, modelFilePath) {
if ((0, utils_2.isObjectField)(field)) {
field = resolveThumbnailPathForModelOrObjectField(field, modelFilePath);
}
else if ((0, utils_2.isEnumField)(field)) {
field = resolveThumbnailPathForEnumField(field, modelFilePath);
}
return field;
}
function resolveThumbnailPathForModelOrObjectField(modelOrField, modelFilePath) {
if (modelOrField.thumbnail && modelFilePath) {
const modelDirPath = path_1.default.dirname(modelFilePath);
modelOrField = {
...modelOrField,
thumbnail: resolveThumbnailPath(modelOrField.thumbnail, modelDirPath)
};
}
return modelOrField;
}
function resolveThumbnailPathForEnumField(enumField, modelFilePath) {
if (enumField.controlType === 'thumbnails' && modelFilePath) {
const modelDirPath = path_1.default.dirname(modelFilePath);
enumField = {
...enumField,
options: lodash_1.default.map(enumField.options, (option) => {
if (option.thumbnail) {
option = {
...option,
thumbnail: resolveThumbnailPath(option.thumbnail, modelDirPath)
};
}
return option;
})
};
}
return enumField;
}
function resolveThumbnailPath(thumbnail, modelDirPath) {
if (thumbnail.startsWith('//') || /https?:\/\//.test(thumbnail)) {
return thumbnail;
}
if (thumbnail.startsWith('/')) {
if (modelDirPath.endsWith('@stackbit/components/models')) {
modelDirPath = modelDirPath.replace(/\/models$/, '');
}
else {
modelDirPath = '';
}
thumbnail = thumbnail.replace(/^\//, '');
}
return path_1.default.join(modelDirPath, thumbnail);
}
//# sourceMappingURL=config-loader-utils.js.map