diff --git a/code/lib/builder-manager/src/utils/data.ts b/code/lib/builder-manager/src/utils/data.ts index b72b7120a3bd..5e315c92e9f8 100644 --- a/code/lib/builder-manager/src/utils/data.ts +++ b/code/lib/builder-manager/src/utils/data.ts @@ -1,11 +1,10 @@ -import { basename, join } from 'path'; +import { basename } from 'path'; import type { DocsOptions, Options } from '@storybook/types'; import { getRefs } from '@storybook/core-common'; import { readTemplate } from './template'; // eslint-disable-next-line import/no-cycle import { executor, getConfig } from '../index'; -import { safeResolve } from './safeResolve'; export const getData = async (options: Options) => { const refs = getRefs(options); @@ -16,7 +15,7 @@ export const getData = async (options: Options) => { const title = options.presets.apply('title'); const docsOptions = options.presets.apply('docs', {}); const template = readTemplate('template.ejs'); - const customHead = safeResolve(join(options.configDir, 'manager-head.html')); + const customHead = options.presets.apply('managerHead'); // we await these, because crucially if these fail, we want to bail out asap const [instance, config] = await Promise.all([ diff --git a/code/lib/builder-manager/src/utils/template.ts b/code/lib/builder-manager/src/utils/template.ts index b959817cfa04..7c9f873794f4 100644 --- a/code/lib/builder-manager/src/utils/template.ts +++ b/code/lib/builder-manager/src/utils/template.ts @@ -1,13 +1,10 @@ -import path, { dirname, join } from 'path'; +import { dirname, join } from 'path'; import fs from 'fs-extra'; import { render } from 'ejs'; import type { DocsOptions, Options, Ref } from '@storybook/types'; -const interpolate = (string: string, data: Record = {}) => - Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string); - export const getTemplatePath = async (template: string) => { return join( dirname(require.resolve('@storybook/builder-manager/package.json')), @@ -17,32 +14,11 @@ export const getTemplatePath = async (template: string) => { }; export const readTemplate = async (template: string) => { - // eslint-disable-next-line @typescript-eslint/no-shadow const path = await getTemplatePath(template); return fs.readFile(path, 'utf8'); }; -export async function getManagerHeadTemplate( - configDirPath: string, - interpolations: Record -) { - const head = await fs - .pathExists(path.resolve(configDirPath, 'manager-head.html')) - .then | false>((exists) => { - if (exists) { - return fs.readFile(path.resolve(configDirPath, 'manager-head.html'), 'utf8'); - } - return false; - }); - - if (!head) { - return ''; - } - - return interpolate(head, interpolations); -} - export async function getManagerMainTemplate() { return getTemplatePath(`manager.ejs`); } @@ -60,7 +36,6 @@ export const renderHTML = async ( docsOptions: Promise, { versionCheck, releaseNotesData, previewUrl, configType }: Options ) => { - const customHeadRef = await customHead; const titleRef = await title; const templateRef = await template; @@ -79,6 +54,6 @@ export const renderHTML = async ( RELEASE_NOTES_DATA: JSON.stringify(JSON.stringify(releaseNotesData), null, 2), PREVIEW_URL: JSON.stringify(previewUrl, null, 2), // global preview URL }, - head: customHeadRef ? await fs.readFile(customHeadRef, 'utf8') : '', + head: (await customHead) || '', }); }; diff --git a/code/lib/core-server/src/presets/common-preset.ts b/code/lib/core-server/src/presets/common-preset.ts index 7b253ac65a86..ec821213808a 100644 --- a/code/lib/core-server/src/presets/common-preset.ts +++ b/code/lib/core-server/src/presets/common-preset.ts @@ -21,6 +21,9 @@ import { dedent } from 'ts-dedent'; import { parseStaticDir } from '../utils/server-statics'; import { defaultStaticDirs } from '../utils/constants'; +const interpolate = (string: string, data: Record = {}) => + Object.entries(data).reduce((acc, [k, v]) => acc.replace(new RegExp(`%${k}%`, 'g'), v), string); + const defaultFavicon = require.resolve('@storybook/core-server/public/favicon.svg'); export const staticDirs: PresetPropertyFn<'staticDirs'> = async (values = []) => [ @@ -217,3 +220,15 @@ export const docs = ( ...docsOptions, docsMode, }); + +export const managerHead = async (_: any, options: Options) => { + const location = join(options.configDir, 'manager-head.html'); + if (await pathExists(location)) { + const contents = readFile(location, 'utf-8'); + const interpolations = options.presets.apply>('env'); + + return interpolate(await contents, await interpolations); + } + + return ''; +}; diff --git a/code/lib/types/src/modules/core-common.ts b/code/lib/types/src/modules/core-common.ts index f45ee3b1d00c..caaa96db041c 100644 --- a/code/lib/types/src/modules/core-common.ts +++ b/code/lib/types/src/modules/core-common.ts @@ -389,13 +389,20 @@ export interface StorybookConfig { previewBody?: PresetValue; /** - * Programatically override the preview's main page template. + * Programmatically override the preview's main page template. * This should return a reference to a file containing an `.ejs` template * that will be interpolated with environment variables. * * @example '.storybook/index.ejs' */ previewMainTemplate?: string; + + /** + * Programmatically modify the preview head/body HTML. + * The managerHead function accept a string, + * which is the existing head content, and return a modified string. + */ + managerHead?: PresetValue; } export type PresetValue = T | ((config: T, options: Options) => T | Promise);