From d3dbdf5657aa7bd639ac4500c9961ea86e0e3229 Mon Sep 17 00:00:00 2001 From: Tobias Bocanegra Date: Tue, 3 Dec 2024 15:58:53 +0100 Subject: [PATCH] fix: apply folder-mapped metadata also for existing resources fixes #758 --- src/PipelineState.d.ts | 19 +++++++++++++++- src/html-pipe.js | 6 ++--- src/steps/extract-metadata.js | 2 +- src/steps/fetch-mapped-metadata.js | 5 ++-- src/steps/folder-mapping.js | 29 ++++++++++++++++-------- test/rendering.test.js | 10 ++++++++ test/steps/fetch-mapped-metadata.test.js | 5 ++++ 7 files changed, 59 insertions(+), 17 deletions(-) diff --git a/src/PipelineState.d.ts b/src/PipelineState.d.ts index 5c5e6caa..47691575 100644 --- a/src/PipelineState.d.ts +++ b/src/PipelineState.d.ts @@ -11,7 +11,7 @@ */ import {PathInfo, S3Loader, PipelineTimer } from "./index"; import {PipelineContent} from "./PipelineContent"; -import {PipelineSiteConfig} from "./site-config"; +import {ModifiersSheet, PipelineSiteConfig} from "./site-config"; declare enum PipelineType { html = 'html', @@ -117,5 +117,22 @@ declare class PipelineState { */ liveHost: string; + /** + * specifies if the content as folder mapped (note that it remains false for content that + * exists below a folder mapped path. in this case, the `mappedPath` would still be different + * from the `info.path` + */ + mapped: boolean; + + /** + * the mapped path (target) of a folder mapping. this is set irrespective of the existence of the + * resource, when the path is below a folder mapped path + */ + mappedPath: string; + + /** + * metadata from folder mapping + */ + mappedMetadata: Modifiers } diff --git a/src/html-pipe.js b/src/html-pipe.js index f6afb926..4ddee834 100644 --- a/src/html-pipe.js +++ b/src/html-pipe.js @@ -18,7 +18,7 @@ import fetchContent from './steps/fetch-content.js'; import fetch404 from './steps/fetch-404.js'; import initConfig from './steps/init-config.js'; import fixSections from './steps/fix-sections.js'; -import folderMapping from './steps/folder-mapping.js'; +import { calculateFolderMapping, applyFolderMapping } from './steps/folder-mapping.js'; import getMetadata from './steps/get-metadata.js'; import html from './steps/make-html.js'; import parseMarkdown from './steps/parse-markdown.js'; @@ -110,7 +110,7 @@ export async function htmlPipe(state, req) { state.content.sourceBus = 'code'; } - // ...and apply the folder mapping + calculateFolderMapping(state); state.timer?.update('content-fetch'); let contentPromise = await fetchContent(state, req, res); if (res.status === 404) { @@ -119,7 +119,7 @@ export async function htmlPipe(state, req) { contentPromise = fetchContentRedirectWith404Fallback(state, req, res); } else { // ...apply folder mapping if the current resource doesn't exist - await folderMapping(state); + applyFolderMapping(state); if (state.info.unmappedPath) { contentPromise = fetchContentWith404Fallback(state, req, res); } else { diff --git a/src/steps/extract-metadata.js b/src/steps/extract-metadata.js index 5be526d7..2e8c2651 100644 --- a/src/steps/extract-metadata.js +++ b/src/steps/extract-metadata.js @@ -174,7 +174,7 @@ export default function extractMetaData(state, req) { // with local metadata from document const metaConfig = Object.assign( state.metadata.getModifiers(state.info.unmappedPath || state.info.path), - state.mappedMetadata.getModifiers(state.info.unmappedPath), + state.mappedMetadata.getModifiers(state.info.unmappedPath || state.info.path), ); // prune empty values and explicit "" strings from sheet based metadata diff --git a/src/steps/fetch-mapped-metadata.js b/src/steps/fetch-mapped-metadata.js index a3704e0f..97d4ce50 100644 --- a/src/steps/fetch-mapped-metadata.js +++ b/src/steps/fetch-mapped-metadata.js @@ -26,11 +26,12 @@ import { Modifiers } from '../utils/modifiers.js'; // eslint-disable-next-line no-unused-vars export default async function fetchMappedMetadata(state, res) { state.mappedMetadata = Modifiers.EMPTY; - if (!state.mapped) { + const { mappedPath } = state; + if (!mappedPath) { return; } const { contentBusId, partition } = state; - const metadataPath = `${state.info.path}/metadata.json`; + const metadataPath = `${mappedPath}/metadata.json`; const key = `${contentBusId}/${partition}${metadataPath}`; const ret = await state.s3Loader.getObject('helix-content-bus', key); if (ret.status === 200) { diff --git a/src/steps/folder-mapping.js b/src/steps/folder-mapping.js index 57d9286b..8c4c8adc 100644 --- a/src/steps/folder-mapping.js +++ b/src/steps/folder-mapping.js @@ -33,26 +33,35 @@ export function mapPath(folders, path) { } /** - * Checks the fstab for folder mapping entries and then re-adjusts the path infos if needed. - * if the remapped resource is *not* extensionless, it will be declared as code-bus resource. + * Checks if the resource path is below a folder-mapped configuration and updates `state.mappedPath` + * accordingly. * - * @type PipelineStep - * @param {PipelineState} state + * @param state */ -export default function folderMapping(state) { +export function calculateFolderMapping(state) { const { folders } = state.config; if (!folders) { return; } const { path } = state.info; - const mapped = mapPath(folders, path); - if (mapped) { - state.info = getPathInfo(mapped); + state.mappedPath = mapPath(folders, path); +} + +/** + * Applies folder mapping if the resource is mapped (i.e. if `state.mappedPath` is {@code true}. + * + * @type PipelineStep + * @param {PipelineState} state + */ +export function applyFolderMapping(state) { + const { info: { path }, mappedPath } = state; + if (mappedPath) { + state.info = getPathInfo(mappedPath); state.info.unmappedPath = path; - if (getExtension(mapped)) { + if (getExtension(mappedPath)) { // special case: use code-bus state.content.sourceBus = 'code'; - state.info.resourcePath = mapped; + state.info.resourcePath = mappedPath; state.log.info(`mapped ${path} to ${state.info.resourcePath} (code-bus)`); } else { state.mapped = true; diff --git a/test/rendering.test.js b/test/rendering.test.js index 7f8014ed..d1218e8e 100644 --- a/test/rendering.test.js +++ b/test/rendering.test.js @@ -643,6 +643,16 @@ describe('Rendering', () => { loader.status('product1.md', 200); resp = await render(new URL('https://helix-pipeline.com/products/product1'), '', 200); assert.match(resp.body, //); + // folder mapped metadata is also applied. + assert.match(resp.body, //); + + assert.deepStrictEqual(Object.fromEntries(resp.headers.entries()), { + 'access-control-allow-origin': '*', + 'content-type': 'text/html; charset=utf-8', + 'last-modified': 'Fri, 30 Apr 2021 03:47:18 GMT', + 'x-surrogate-key': 'AkcHu8fRFT7HarTR foo-id_metadata super-test--helix-pages--adobe_head foo-id AkcHu8fRFT7HarTR_metadata z8NGXvKB0X5Fzcnd', + link: '; rel=modulepreload; as=script; crossorigin=use-credentials', + }); }); it('renders 404', async () => { diff --git a/test/steps/fetch-mapped-metadata.test.js b/test/steps/fetch-mapped-metadata.test.js index 5fb28279..49f82783 100644 --- a/test/steps/fetch-mapped-metadata.test.js +++ b/test/steps/fetch-mapped-metadata.test.js @@ -24,6 +24,7 @@ describe('Fetch Mapped Metadata', () => { contentBusId: 'foo-id', partition: 'live', mapped: true, + mappedPath: '/mapped', info: { path: '/mapped', }, @@ -44,6 +45,7 @@ describe('Fetch Mapped Metadata', () => { contentBusId: 'foo-id', partition: 'live', mapped: true, + mappedPath: '/mapped', info: { path: '/mapped', }, @@ -63,6 +65,7 @@ describe('Fetch Mapped Metadata', () => { contentBusId: 'foo-id', partition: 'live', mapped: true, + mappedPath: '/mapped', info: { path: '/mapped', }, @@ -81,6 +84,7 @@ describe('Fetch Mapped Metadata', () => { log: console, contentBusId: 'foo-id', partition: 'live', + mappedPath: '/mapped', mapped: true, info: { path: '/mapped', @@ -102,6 +106,7 @@ describe('Fetch Mapped Metadata', () => { contentBusId: 'foo-id', partition: 'live', mapped: true, + mappedPath: '/mapped', info: { path: '/mapped', },