@adobe/helix-html-pipeline 5.13.8 → 5.13.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/CHANGELOG.md CHANGED
@@ -1,3 +1,17 @@
1
+ ## [5.13.10](https://github.com/adobe/helix-html-pipeline/compare/v5.13.9...v5.13.10) (2024-11-18)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * respect last-modified from mapped metadata ([e77e3b6](https://github.com/adobe/helix-html-pipeline/commit/e77e3b6392e6e49e1ea5220ae4a278122b24a5df)), closes [#737](https://github.com/adobe/helix-html-pipeline/issues/737)
7
+
8
+ ## [5.13.9](https://github.com/adobe/helix-html-pipeline/compare/v5.13.8...v5.13.9) (2024-11-18)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency @adobe/helix-markdown-support to v7.1.8 ([#751](https://github.com/adobe/helix-html-pipeline/issues/751)) ([d57d3a8](https://github.com/adobe/helix-html-pipeline/commit/d57d3a8517bbc68c328ce39080531f28dcc26a0a))
14
+
1
15
  ## [5.13.8](https://github.com/adobe/helix-html-pipeline/compare/v5.13.7...v5.13.8) (2024-11-17)
2
16
 
3
17
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-html-pipeline",
3
- "version": "5.13.8",
3
+ "version": "5.13.10",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -43,7 +43,7 @@
43
43
  "node": ">=16.x"
44
44
  },
45
45
  "dependencies": {
46
- "@adobe/helix-markdown-support": "7.1.7",
46
+ "@adobe/helix-markdown-support": "7.1.8",
47
47
  "@adobe/helix-shared-utils": "3.0.2",
48
48
  "@adobe/mdast-util-gridtables": "4.0.8",
49
49
  "@adobe/remark-gridtables": "3.0.8",
@@ -58,6 +58,7 @@
58
58
  "lodash.escape": "4.0.1",
59
59
  "mdast-util-to-hast": "13.2.0",
60
60
  "mdast-util-to-string": "4.0.0",
61
+ "micromark-util-subtokenize": "2.0.1",
61
62
  "mime": "4.0.4",
62
63
  "rehype-format": "5.0.1",
63
64
  "rehype-parse": "9.0.1",
@@ -30,7 +30,7 @@ export class PipelineResponse {
30
30
  document: undefined,
31
31
  headers,
32
32
  error: undefined,
33
- lastModifiedTime: 0,
33
+ lastModifiedSources: {},
34
34
  });
35
35
  }
36
36
 
package/src/html-pipe.js CHANGED
@@ -38,6 +38,7 @@ import { PipelineResponse } from './PipelineResponse.js';
38
38
  import { validatePathInfo } from './utils/path.js';
39
39
  import { initAuthRoute } from './utils/auth.js';
40
40
  import fetchMappedMetadata from './steps/fetch-mapped-metadata.js';
41
+ import { applyMetaLastModified, setLastModified } from './utils/last-modified.js';
41
42
 
42
43
  /**
43
44
  * Fetches the content and if not found, fetches the 404.html
@@ -162,6 +163,7 @@ export async function htmlPipe(state, req) {
162
163
  log[level](`pipeline status: ${res.status} ${res.error}`);
163
164
  res.headers.set('x-error', cleanupHeaderValue(res.error));
164
165
  if (res.status < 500) {
166
+ setLastModified(state, res);
165
167
  await setCustomResponseHeaders(state, req, res);
166
168
  }
167
169
  return res;
@@ -188,8 +190,10 @@ export async function htmlPipe(state, req) {
188
190
  await render(state, req, res);
189
191
  state.timer?.update('serialize');
190
192
  await tohtml(state, req, res);
193
+ await applyMetaLastModified(state, res);
191
194
  }
192
195
 
196
+ setLastModified(state, res);
193
197
  await setCustomResponseHeaders(state, req, res);
194
198
  await setXSurrogateKeyHeader(state, req, res);
195
199
  } catch (e) {
package/src/json-pipe.js CHANGED
@@ -14,7 +14,7 @@ import fetchConfigAll from './steps/fetch-config-all.js';
14
14
  import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
15
15
  import { PipelineResponse } from './PipelineResponse.js';
16
16
  import jsonFilter from './utils/json-filter.js';
17
- import { extractLastModified, updateLastModified } from './utils/last-modified.js';
17
+ import { extractLastModified, recordLastModified, setLastModified } from './utils/last-modified.js';
18
18
  import { authenticate } from './steps/authenticate.js';
19
19
  import fetchConfig from './steps/fetch-config.js';
20
20
  import { getPathInfo } from './utils/path.js';
@@ -85,7 +85,7 @@ async function fetchJsonContent(state, req, res) {
85
85
  state.content.sourceLocation = ret.headers.get('x-amz-meta-x-source-location');
86
86
  log.info(`source-location: ${state.content.sourceLocation}`);
87
87
 
88
- updateLastModified(state, res, extractLastModified(ret.headers));
88
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
89
89
  } else {
90
90
  // also add code surrogate key in case json is later added to code bus (#688)
91
91
  state.content.sourceBus = 'code|content';
@@ -195,6 +195,7 @@ export async function jsonPipe(state, req) {
195
195
  const keys = await computeSurrogateKeys(state);
196
196
  res.headers.set('x-surrogate-key', keys.join(' '));
197
197
 
198
+ setLastModified(state, res);
198
199
  await setCustomResponseHeaders(state, req, res);
199
200
  return res;
200
201
  } catch (e) {
@@ -20,6 +20,7 @@ import setXSurrogateKeyHeader from './steps/set-x-surrogate-key-header.js';
20
20
  import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
21
21
  import { PipelineStatusError } from './PipelineStatusError.js';
22
22
  import { PipelineResponse } from './PipelineResponse.js';
23
+ import { extractLastModified, recordLastModified, setLastModified } from './utils/last-modified.js';
23
24
 
24
25
  async function generateSitemap(state) {
25
26
  const {
@@ -118,6 +119,7 @@ export async function sitemapPipe(state, req) {
118
119
  const ret = await generateSitemap(state);
119
120
  if (ret.status === 200) {
120
121
  res.status = 200;
122
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
121
123
  delete res.error;
122
124
  state.content.data = ret.body;
123
125
  }
@@ -129,6 +131,7 @@ export async function sitemapPipe(state, req) {
129
131
 
130
132
  state.timer?.update('serialize');
131
133
  await renderCode(state, req, res);
134
+ setLastModified(state, res);
132
135
  await setCustomResponseHeaders(state, req, res);
133
136
  await setXSurrogateKeyHeader(state, req, res);
134
137
  } catch (e) {
@@ -9,7 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { extractLastModified } from '../utils/last-modified.js';
12
+ import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
13
13
  import { getPathKey } from './set-x-surrogate-key-header.js';
14
14
 
15
15
  /**
@@ -29,7 +29,7 @@ export default async function fetch404(state, req, res) {
29
29
  // override last-modified if source-last-modified is set
30
30
  const lastModified = extractLastModified(ret.headers);
31
31
  if (lastModified) {
32
- ret.headers.set('last-modified', lastModified);
32
+ recordLastModified(state, res, 'content', lastModified);
33
33
  }
34
34
 
35
35
  // keep 404 response status
@@ -11,7 +11,7 @@
11
11
  */
12
12
 
13
13
  import { PipelineStatusError } from '../PipelineStatusError.js';
14
- import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
14
+ import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
15
15
  import { globToRegExp, Modifiers } from '../utils/modifiers.js';
16
16
  import { getOriginalHost } from './utils.js';
17
17
 
@@ -74,7 +74,7 @@ export default async function fetchConfigAll(state, req, res) {
74
74
 
75
75
  if (state.type === 'html' && state.info.selector !== 'plain') {
76
76
  // also update last-modified (only for extensionless html pipeline)
77
- updateLastModified(state, res, extractLastModified(ret.headers));
77
+ recordLastModified(state, res, 'configAll', extractLastModified(ret.headers));
78
78
  }
79
79
  // set custom preview and live hosts
80
80
  state.previewHost = replaceParams(state.config.cdn?.preview?.host, state);
@@ -9,7 +9,7 @@
9
9
  * OF ANY KIND, either express or implied. See the License for the specific language
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
- import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
12
+ import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
13
13
  import { PipelineStatusError } from '../PipelineStatusError.js';
14
14
 
15
15
  /**
@@ -67,11 +67,11 @@ export default async function fetchConfig(state, req, res) {
67
67
  const configLastModified = extractLastModified(ret.headers);
68
68
 
69
69
  // update last modified of fstab
70
- updateLastModified(state, res, config.fstab?.lastModified || configLastModified);
70
+ recordLastModified(state, res, 'config', config.fstab?.lastModified || configLastModified);
71
71
 
72
72
  // for html requests, also consider the HEAD config
73
73
  if (state.type === 'html' && state.info.selector !== 'plain' && config.head?.lastModified) {
74
- updateLastModified(state, res, config.head.lastModified);
74
+ recordLastModified(state, res, 'head', config.head.lastModified);
75
75
  }
76
76
  }
77
77
 
@@ -10,7 +10,7 @@
10
10
  * governing permissions and limitations under the License.
11
11
  */
12
12
  import { computeSurrogateKey } from '@adobe/helix-shared-utils';
13
- import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
13
+ import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
14
14
 
15
15
  /**
16
16
  * Loads the content from either the content-bus or code-bus and stores it in `state.content`
@@ -67,7 +67,7 @@ export default async function fetchContent(state, req, res) {
67
67
  state.content.sourceLocation = ret.headers.get('x-amz-meta-x-source-location');
68
68
  log.info(`source-location: ${state.content.sourceLocation}`);
69
69
 
70
- updateLastModified(state, res, extractLastModified(ret.headers));
70
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
71
71
 
72
72
  // reject requests to /index *after* checking for redirects
73
73
  // (https://github.com/adobe/helix-pipeline-service/issues/290)
@@ -11,30 +11,43 @@
11
11
  */
12
12
 
13
13
  /**
14
- * Updates the context.content.lastModified if the time in `timeString` is newer than the existing
15
- * one if none exists yet. please note that it generates helper property `lastModifiedTime` in
16
- * unix epoch format.
17
- *
18
- * the date string will be a "http-date": https://httpwg.org/specs/rfc7231.html#http.date
14
+ * Records the last modified for the given source.
19
15
  *
20
16
  * @param {PipelineState} state
21
- * @param {PipelineResponse} res the pipeline context
17
+ * @param {PipelineResponse} res the pipeline response
18
+ * @param {string} source the source providing a last-modified date
22
19
  * @param {string} httpDate http-date string
23
20
  */
24
- export function updateLastModified(state, res, httpDate) {
21
+ export function recordLastModified(state, res, source, httpDate) {
25
22
  if (!httpDate) {
26
23
  return;
27
24
  }
28
25
  const { log } = state;
29
- const time = new Date(httpDate).getTime();
30
- if (Number.isNaN(time)) {
31
- log.warn(`updateLastModified date is invalid: ${httpDate}`);
26
+ const date = new Date(httpDate);
27
+ if (Number.isNaN(date.valueOf())) {
28
+ log.warn(`last-modified date is invalid: ${httpDate} for ${source}`);
32
29
  return;
33
30
  }
31
+ res.lastModifiedSources[source] = {
32
+ time: date.valueOf(),
33
+ date: date.toUTCString(),
34
+ };
35
+ }
34
36
 
35
- if (time > (res.lastModifiedTime ?? 0)) {
36
- res.lastModifiedTime = time;
37
- res.headers.set('last-modified', httpDate);
37
+ /**
38
+ * Calculates the last modified by using the latest date from all the recorded sources
39
+ * and sets it on the `last-modified` header.
40
+ *
41
+ * @param {PipelineState} state
42
+ * @param {PipelineResponse} res the pipeline response
43
+ */
44
+ export function setLastModified(state, res) {
45
+ let latestTime = 0;
46
+ for (const { time, date } of Object.values(res.lastModifiedSources)) {
47
+ if (time > latestTime) {
48
+ latestTime = time;
49
+ res.headers.set('last-modified', date);
50
+ }
38
51
  }
39
52
  }
40
53
 
@@ -55,3 +68,13 @@ export function extractLastModified(headers) {
55
68
  }
56
69
  return headers.get('last-modified');
57
70
  }
71
+
72
+ /**
73
+ * Sets the metadata last modified entry to the one define in the page specific metadata if
74
+ * it exists. this allows to control the last-modified per metadata record.
75
+ * @param {PipelineState} state
76
+ * @param {PipelineResponse} res the pipeline response
77
+ */
78
+ export function applyMetaLastModified(state, res) {
79
+ recordLastModified(state, res, 'metadata', state.content.meta.page['last-modified']);
80
+ }