packages/presetter/source/content.ts
/*
* *** MIT LICENSE ***
* -------------------------------------------------------------------------
* This code may be modified and distributed under the MIT license.
* See the LICENSE file for details.
* -------------------------------------------------------------------------
*
* @summary Collection of context resolvers
*
* @author Alvis HT Tang <alvis@hilbert.space>
* @license MIT
* @copyright Copyright (c) 2021 - All Rights Reserved.
* -------------------------------------------------------------------------
*/
import { getConfigKey, loadDynamic, loadDynamicMap } from './resolution';
import { filter, merge, mergeTemplate, template } from './template';
import type {
Config,
PresetContext,
PresetGraph,
PresetNode,
PresetterConfig,
ResolvedPresetContext,
Template,
} from 'presetter-types';
/**
* enrich the context with the resolved supplementary assets
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns enriched preset context
*/
export async function resolveContext(_: {
graph: PresetGraph;
context: PresetContext;
}): Promise<ResolvedPresetContext> {
const { graph } = _;
// compute a new context with variable resolved
const context: ResolvedPresetContext<'variable'> = {
..._.context,
custom: {
..._.context.custom,
variable: resolveVariable({ graph, config: _.context.custom }),
},
};
const config = await resolveSupplementaryConfig({ graph, context });
const noSymlinks = await resolveNoSymlinks({ graph, context });
const scripts = await resolveSupplementaryScripts({ graph, context });
// return a new context with everything resolved
return {
target: context.target,
custom: {
...context.custom,
preset: context.custom.preset,
config,
noSymlinks,
scripts,
},
};
}
/**
* resolve no noSymlinks
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns list of noSymlinks
*/
export async function resolveNoSymlinks(_: {
graph: PresetGraph;
context: ResolvedPresetContext<'variable'>;
}): Promise<string[]> {
const { graph, context } = _;
const fromPreset = (
await Promise.all(
graph.map(async (node) => resolveNoSymlinksFromNode({ node, context })),
)
).flat();
const fromUser = context.custom.noSymlinks ?? [];
return [...new Set([...fromPreset, ...fromUser])];
}
/**
* resolve noSymlinks from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @param _.context resolved preset context
* @returns list of noSymlinks
*/
async function resolveNoSymlinksFromNode(_: {
node: PresetNode;
context: ResolvedPresetContext<'variable'>;
}): Promise<string[]> {
const { node, context } = _;
const { asset, nodes } = node;
// resolve noSymlink lists from preset's extensions
const fromChildren = (
await Promise.all(
nodes.map(async (extension) =>
resolveNoSymlinksFromNode({ node: extension, context }),
),
)
).flat();
// resolve preset's noSymlink list
const fromPreset = await loadDynamic(asset.noSymlinks ?? [], context);
return [...new Set([...fromChildren, ...fromPreset])];
}
/**
* compute the final config map
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns map of config content
*/
export async function resolveSupplementaryConfig(_: {
graph: PresetGraph;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, Config>> {
const { graph, context } = _;
const fromPresets = (
await Promise.all(
graph.map(async (node) =>
resolveSupplementaryConfigFromNode({ node, context }),
),
)
).reduce((merged, next) => merge(merged, next), {});
return merge(fromPresets, context.custom.config);
}
/**
* compute the supplementary config map from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @param _.context preset context
* @returns map of config content
*/
export async function resolveSupplementaryConfigFromNode(_: {
node: PresetNode;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, Config>> {
const { node, context } = _;
const { asset, nodes } = node;
// resolve configs from the preset's extensions
const fromChildren = (
await Promise.all(
nodes.map(async (node) =>
resolveSupplementaryConfigFromNode({ node, context }),
),
)
).reduce((merged, next) => merge(merged, next), {});
// resolve preset's config
const fromPreset = await loadDynamicMap<'supplementaryConfig'>(
asset.supplementaryConfig,
context,
);
// merge preset's config on top of the extensions
return merge(fromChildren, fromPreset);
}
/**
* compute script that will be merged with those provided by presets
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns map of config content
*/
export async function resolveSupplementaryScripts(_: {
graph: PresetGraph;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, string>> {
const { graph, context } = _;
const fromPresets = (
await Promise.all(
graph.map(async (node) =>
resolveSupplementaryScriptsFromNode({ node, context }),
),
)
).reduce((merged, next) => merge(merged, next), {});
return merge(fromPresets, context.custom.scripts);
}
/**
* compute the supplementary config map from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @param _.context preset context
* @returns map of config content
*/
export async function resolveSupplementaryScriptsFromNode(_: {
node: PresetNode;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, string>> {
const { node, context } = _;
const { asset, nodes } = node;
const { supplementaryScripts } = asset;
// resolve configs from the preset's extensions
const fromChildren = (
await Promise.all(
nodes.map(async (node) =>
resolveSupplementaryScriptsFromNode({ node, context }),
),
)
).reduce((merged, next) => merge(merged, next), {});
// resolve preset's config
const fromPreset = await loadDynamic(supplementaryScripts ?? {}, context);
// merge preset's config on top of the extensions
return merge(fromChildren, fromPreset);
}
/**
* combine default variables from presets with custom variables
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.config user config
* @returns combined variables
*/
export function resolveVariable(_: {
graph: PresetGraph;
config: PresetterConfig;
}): Record<string, string> {
const { graph, config } = _;
// get the default from presets
const fromPresets = graph
.map((node) => resolveVariableFromNode({ node }))
.reduce((merged, next) => merge(merged, next), {});
// merge with those from the config file
return merge(fromPresets, config.variable);
}
/**
* resolve variables from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @returns combined variables
*/
function resolveVariableFromNode(_: {
node: PresetNode;
}): Record<string, string> {
const { node } = _;
const { asset, nodes } = node;
// resolve variables from the preset's extensions
const fromChildren = nodes
.map((node) => resolveVariableFromNode({ node }))
.reduce((merged, next) => merge(merged, next), {});
// merge with the preset's default variables
return merge(fromChildren, asset.variable);
}
/**
* compute the final script map
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns map of script content
*/
export async function resolveScripts(_: {
graph: PresetGraph;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, string>> {
const { graph, context } = _;
// resolve scripts from all presets
const fromPresets = (
await Promise.all(
graph.map(async (node) => resolveScriptsFromNode({ node, context })),
)
).reduce((merged, next) => merge(merged, next), {});
const fromConfig = context.custom.scripts;
return template(merge(fromPresets, fromConfig), context.custom.variable);
}
/**
* compute the final script map from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @param _.context preset context
* @returns map of script content
*/
export async function resolveScriptsFromNode(_: {
node: PresetNode;
context: ResolvedPresetContext<'variable'>;
}): Promise<Record<string, string>> {
const { node, context } = _;
const { asset, nodes } = node;
// resolve scripts from the preset's extensions
const fromChildren = (
await Promise.all(
nodes.map(async (node) => resolveScriptsFromNode({ node, context })),
)
).reduce((merged, next) => merge(merged, next), {});
// resolve preset's scripts
const fromPreset = await loadDynamic(asset.scripts ?? {}, context);
// merge preset's scripts on top of the extensions
return merge(fromChildren, fromPreset);
}
/**
* compute the final template content
* @param _ collection of arguments
* @param _.graph preset graph
* @param _.context preset context
* @returns map of template content
*/
export async function resolveTemplate(_: {
graph: PresetGraph;
context: ResolvedPresetContext;
}): Promise<Record<string, Template>> {
const { graph, context } = _;
// deduce all the template contents and their paths from presets
const fromPreset = (
await Promise.all(
graph.map(async (node) => resolveTemplateFromNode({ node, context })),
)
).reduce((merged, next) => mergeTemplate(merged, next), {});
// merge the template with the config supplied by user
const customTemplate = Object.fromEntries(
Object.entries(fromPreset).map(([path, current]) => {
const config = context.custom.config[getConfigKey(path)] as Partial<
typeof context.custom.config
>[string];
const candidate = Array.isArray(config) ? config.join('\n') : config;
return [path, candidate ?? current];
}),
);
const merged = mergeTemplate(fromPreset, customTemplate);
const resolvedTemplate = filter(merged, ...(context.custom.ignores ?? []));
return template(resolvedTemplate, context.custom.variable);
}
/**
* compute the final template content from a preset node
* @param _ collection of arguments
* @param _.node preset node
* @param _.context preset context
* @returns map of template content
*/
export async function resolveTemplateFromNode(_: {
node: PresetNode;
context: ResolvedPresetContext;
}): Promise<Record<string, Template>> {
const { node, context } = _;
const { asset, nodes } = node;
const { supplementaryIgnores } = asset;
// resolve template from the preset's extensions
const fromChildren = (
await Promise.all(
nodes.map(async (node) => resolveTemplateFromNode({ node, context })),
)
).reduce((current, next) => mergeTemplate(current, next), {});
const fromPreset = await loadDynamicMap<'template'>(asset.template, context);
const merged = mergeTemplate(fromChildren, fromPreset);
const ignoreRules =
typeof supplementaryIgnores === 'function'
? await supplementaryIgnores(context)
: supplementaryIgnores;
return filter(merged, ...(ignoreRules ?? []));
}