@adobe/helix-html-pipeline 6.15.10 → 6.16.0

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
+ # [6.16.0](https://github.com/adobe/helix-html-pipeline/compare/v6.15.11...v6.16.0) (2024-11-18)
2
+
3
+
4
+ ### Features
5
+
6
+ * respect last-modified from metadata sheet ([#744](https://github.com/adobe/helix-html-pipeline/issues/744)) ([50b3e07](https://github.com/adobe/helix-html-pipeline/commit/50b3e07d909651368e4b5e2c473391b63aa05913)), closes [#743](https://github.com/adobe/helix-html-pipeline/issues/743)
7
+
8
+ ## [6.15.11](https://github.com/adobe/helix-html-pipeline/compare/v6.15.10...v6.15.11) (2024-11-18)
9
+
10
+
11
+ ### Bug Fixes
12
+
13
+ * **deps:** update dependency @adobe/helix-markdown-support to v7.1.8 ([#749](https://github.com/adobe/helix-html-pipeline/issues/749)) ([6e57524](https://github.com/adobe/helix-html-pipeline/commit/6e575244088b5c43cc5c6f6d7e5ae6f6b7ee8d0c))
14
+
1
15
  ## [6.15.10](https://github.com/adobe/helix-html-pipeline/compare/v6.15.9...v6.15.10) (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": "6.15.10",
3
+ "version": "6.16.0",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -42,7 +42,7 @@
42
42
  "node": ">=16.x"
43
43
  },
44
44
  "dependencies": {
45
- "@adobe/helix-markdown-support": "7.1.7",
45
+ "@adobe/helix-markdown-support": "7.1.8",
46
46
  "@adobe/helix-shared-utils": "3.0.2",
47
47
  "@adobe/mdast-util-gridtables": "4.0.8",
48
48
  "@adobe/remark-gridtables": "3.0.8",
@@ -55,6 +55,7 @@
55
55
  "lodash.escape": "4.0.1",
56
56
  "mdast-util-to-hast": "13.2.0",
57
57
  "mdast-util-to-string": "4.0.0",
58
+ "micromark-util-subtokenize": "2.0.1",
58
59
  "mime": "4.0.4",
59
60
  "rehype-format": "5.0.1",
60
61
  "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
@@ -35,6 +35,7 @@ import { PipelineStatusError } from './PipelineStatusError.js';
35
35
  import { PipelineResponse } from './PipelineResponse.js';
36
36
  import { validatePathInfo } from './utils/path.js';
37
37
  import fetchMappedMetadata from './steps/fetch-mapped-metadata.js';
38
+ import { applyMetaLastModified, setLastModified } from './utils/last-modified.js';
38
39
 
39
40
  /**
40
41
  * Fetches the content and if not found, fetches the 404.html
@@ -140,6 +141,7 @@ export async function htmlPipe(state, req) {
140
141
  log[level](`pipeline status: ${res.status} ${res.error}`);
141
142
  res.headers.set('x-error', cleanupHeaderValue(res.error));
142
143
  if (res.status < 500) {
144
+ setLastModified(state, res);
143
145
  await setCustomResponseHeaders(state, req, res);
144
146
  }
145
147
  return res;
@@ -166,8 +168,10 @@ export async function htmlPipe(state, req) {
166
168
  await render(state, req, res);
167
169
  state.timer?.update('serialize');
168
170
  await tohtml(state, req, res);
171
+ await applyMetaLastModified(state, res);
169
172
  }
170
173
 
174
+ setLastModified(state, res);
171
175
  await setCustomResponseHeaders(state, req, res);
172
176
  await setXSurrogateKeyHeader(state, req, res);
173
177
  } catch (e) {
package/src/json-pipe.js CHANGED
@@ -14,7 +14,7 @@ import initConfig from './steps/init-config.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 { getPathInfo } from './utils/path.js';
19
19
  import { PipelineStatusError } from './PipelineStatusError.js';
20
20
 
@@ -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';
@@ -191,6 +191,7 @@ export async function jsonPipe(state, req) {
191
191
  const keys = await computeSurrogateKeys(state);
192
192
  res.headers.set('x-surrogate-key', keys.join(' '));
193
193
 
194
+ setLastModified(state, res);
194
195
  await setCustomResponseHeaders(state, req, res);
195
196
  return res;
196
197
  } catch (e) {
@@ -18,7 +18,7 @@ import setCustomResponseHeaders from './steps/set-custom-response-headers.js';
18
18
  import { PipelineStatusError } from './PipelineStatusError.js';
19
19
  import { PipelineResponse } from './PipelineResponse.js';
20
20
  import initConfig from './steps/init-config.js';
21
- import { extractLastModified, updateLastModified } from './utils/last-modified.js';
21
+ import { extractLastModified, recordLastModified, setLastModified } from './utils/last-modified.js';
22
22
 
23
23
  async function generateSitemap(state) {
24
24
  const {
@@ -103,7 +103,7 @@ export async function sitemapPipe(state, req) {
103
103
  const ret = await generateSitemap(state);
104
104
  if (ret.status === 200) {
105
105
  res.status = 200;
106
- updateLastModified(state, res, extractLastModified(ret.headers));
106
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
107
107
  delete res.error;
108
108
  state.content.data = ret.body;
109
109
  }
@@ -115,6 +115,7 @@ export async function sitemapPipe(state, req) {
115
115
 
116
116
  state.timer?.update('serialize');
117
117
  await renderCode(state, req, res);
118
+ setLastModified(state, res);
118
119
  await setCustomResponseHeaders(state, req, res);
119
120
  await setXSurrogateKeyHeader(state, req, res);
120
121
  } 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
@@ -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`
@@ -65,7 +65,7 @@ export default async function fetchContent(state, req, res) {
65
65
  state.content.sourceLocation = ret.headers.get('x-amz-meta-x-source-location');
66
66
  log.info(`source-location: ${state.content.sourceLocation}`);
67
67
 
68
- updateLastModified(state, res, extractLastModified(ret.headers));
68
+ recordLastModified(state, res, 'content', extractLastModified(ret.headers));
69
69
 
70
70
  // reject requests to /index *after* checking for redirects
71
71
  // (https://github.com/adobe/helix-pipeline-service/issues/290)
@@ -12,7 +12,7 @@
12
12
 
13
13
  import { PipelineStatusError } from '../PipelineStatusError.js';
14
14
  import { Modifiers } from '../utils/modifiers.js';
15
- import { extractLastModified, updateLastModified } from '../utils/last-modified.js';
15
+ import { extractLastModified, recordLastModified } from '../utils/last-modified.js';
16
16
 
17
17
  /**
18
18
  * Loads metadata for a mapped path and puts it into `state.mappedMetadata`. If path is not
@@ -53,7 +53,7 @@ export default async function fetchMappedMetadata(state, res) {
53
53
  state.mappedMetadata = Modifiers.fromModifierSheet(
54
54
  data,
55
55
  );
56
- updateLastModified(state, res, extractLastModified(ret.headers));
56
+ recordLastModified(state, res, 'metadata', extractLastModified(ret.headers));
57
57
  return;
58
58
  }
59
59
  if (ret.status !== 404) {
@@ -11,7 +11,7 @@
11
11
  */
12
12
  import { Modifiers } from '../utils/modifiers.js';
13
13
  import { getOriginalHost } from './utils.js';
14
- import { updateLastModified } from '../utils/last-modified.js';
14
+ import { recordLastModified } from '../utils/last-modified.js';
15
15
 
16
16
  function replaceParams(str, info) {
17
17
  if (!str) {
@@ -44,5 +44,5 @@ export default function initConfig(state, req, res) {
44
44
  state.previewHost = replaceParams(config.cdn?.preview?.host, state);
45
45
  state.liveHost = replaceParams(config.cdn?.live?.host, state);
46
46
  state.prodHost = config.cdn?.prod?.host || getOriginalHost(req.headers);
47
- updateLastModified(state, res, state.config.lastModified);
47
+ recordLastModified(state, res, 'config', state.config.lastModified);
48
48
  }
@@ -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
+ }