@stackbit/sdk
Version:
136 lines (123 loc) • 4.92 kB
text/typescript
import _ from 'lodash';
import path from 'path';
import fse from 'fs-extra';
import { parseFile, append } from '@stackbit/utils';
import { ModelWithSource } from '@stackbit/types';
import { Config, Model, Preset } from './config-types';
import { ConfigPresetsError } from './config-errors';
interface RawPresetData {
model: string;
srcType: string;
srcProjectId: string;
presets: RawPreset[];
}
interface RawPreset {
label: string;
thumbnail?: string;
locale?: string;
data: Record<string, any>;
}
export async function loadPresets({
config,
fallbackSrcType,
fallbackSrcProjectId
}: {
config: Config;
fallbackSrcType?: string;
fallbackSrcProjectId?: string;
}): Promise<{
presets: Record<string, Preset>;
errors: ConfigPresetsError[];
}> {
const presetFiles = [];
const presetsRelDirs = getPresetDirs(config);
for (const presetsRelDir of presetsRelDirs) {
const presetsDir = path.join(config.dirPath, presetsRelDir);
if (!(await fse.pathExists(presetsDir))) {
continue;
}
const files = (await fse.readdir(presetsDir))
.filter((fileName) => ['.json', '.yaml', '.yml'].includes(path.parse(fileName).ext))
.map((fileName) => path.join(presetsRelDir, fileName));
presetFiles.push(...files);
}
const presets: Record<string, Preset> = {};
const errors: ConfigPresetsError[] = [];
for (const presetFile of presetFiles) {
const presetsRelDir = path.dirname(presetFile);
const presetPath = path.join(config.dirPath, presetFile);
let presetData: RawPresetData;
try {
presetData = await parseFile(presetPath);
} catch (err: any) {
errors.push(new ConfigPresetsError(`Error parsing ${presetFile} (${err?.message})`));
continue;
}
const srcType = presetData.srcType ?? fallbackSrcType;
const srcProjectId = presetData.srcProjectId ?? fallbackSrcProjectId;
_.forEach(_.get(presetData, 'presets', []), (preset, i) => {
const presetId = `${presetFile}:presets[${i}]`;
const { thumbnail, ...rest } = preset;
presets[presetId] = {
...rest,
...(thumbnail ? { thumbnail: resolveThumbnailPath(thumbnail, presetsRelDir) } : null),
modelName: presetData.model,
...(srcType ? { srcType } : null),
...(srcProjectId ? { srcProjectId } : null)
} as Preset;
});
}
return {
presets,
errors
};
}
export function getPresetDirs(config: Config): string[] {
const defaultPresetsDirs = ['.stackbit/presets', 'node_modules/@stackbit/components/presets'];
if (config.presetSource?.type === 'files' && config.presetSource?.presetDirs) {
return [...new Set([...defaultPresetsDirs, ...config.presetSource.presetDirs])];
}
return defaultPresetsDirs;
}
export function extendModelsWithPresetsIds<T extends Model | ModelWithSource>({ models, presets }: { models: T[]; presets: Record<string, Preset> }): T[] {
// for older projects that pass the Model, we assume that all presets belong to the same content-source
const presetsIdsByContentSourceAndModel: Record<string, Record<string, Record<string, string[]>>> = {};
const presetsIdsByModel_deprecated: Record<string, string[]> = {};
for (const [presetId, preset] of Object.entries(presets)) {
append(presetsIdsByContentSourceAndModel, [preset.srcType, preset.srcProjectId, preset.modelName], presetId);
append(presetsIdsByModel_deprecated, preset.modelName, presetId);
}
// update models with presets IDs
return models.map((model) => {
let presetIdsForModel: string[] | undefined;
if ('srcType' in model && 'srcProjectId' in model) {
// "as" can be removed in typescript 4.3.5 and above
const srcType = (model as ModelWithSource).srcType;
const srcProjectId = (model as ModelWithSource).srcProjectId;
presetIdsForModel = presetsIdsByContentSourceAndModel[srcType]?.[srcProjectId]?.[model.name];
} else {
presetIdsForModel = presetsIdsByModel_deprecated[model.name];
}
if (!presetIdsForModel) {
return model;
}
return {
...model,
presets: presetIdsForModel
};
});
}
function resolveThumbnailPath(thumbnail: string, dir: string) {
if (thumbnail.startsWith('//') || /https?:\/\//.test(thumbnail)) {
return thumbnail;
}
if (thumbnail.startsWith('/')) {
if (dir.endsWith('@stackbit/components/presets')) {
dir = dir.replace(/\/presets$/, '');
} else {
dir = '';
}
thumbnail = thumbnail.replace(/^\//, '');
}
return path.join(dir, thumbnail);
}