@adobe/helix-html-pipeline 4.1.5 → 5.0.1

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,22 @@
1
+ ## [5.0.1](https://github.com/adobe/helix-html-pipeline/compare/v5.0.0...v5.0.1) (2023-09-09)
2
+
3
+
4
+ ### Bug Fixes
5
+
6
+ * properly handle empty string ([#395](https://github.com/adobe/helix-html-pipeline/issues/395)) ([daac492](https://github.com/adobe/helix-html-pipeline/commit/daac49224642eeb69951ee68256fcb7f264f4f0c))
7
+
8
+ # [5.0.0](https://github.com/adobe/helix-html-pipeline/compare/v4.1.5...v5.0.0) (2023-09-09)
9
+
10
+
11
+ ### Features
12
+
13
+ * better metadata fallback ([2c7481c](https://github.com/adobe/helix-html-pipeline/commit/2c7481c19c52cce38559a50360c8c5eabc201b27)), closes [#382](https://github.com/adobe/helix-html-pipeline/issues/382)
14
+
15
+
16
+ ### BREAKING CHANGES
17
+
18
+ * metadata fallback is different for empty values.
19
+
1
20
  ## [4.1.5](https://github.com/adobe/helix-html-pipeline/compare/v4.1.4...v4.1.5) (2023-09-02)
2
21
 
3
22
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@adobe/helix-html-pipeline",
3
- "version": "4.1.5",
3
+ "version": "5.0.1",
4
4
  "description": "Helix HTML Pipeline",
5
5
  "main": "src/index.js",
6
6
  "types": "src/index.d.ts",
@@ -29,7 +29,10 @@
29
29
  },
30
30
  "homepage": "https://github.com/adobe/helix-html-pipeline#readme",
31
31
  "mocha": {
32
- "require": "test/setup-env.js",
32
+ "require": [
33
+ "test/setup-env.js",
34
+ "mocha-suppress-logs"
35
+ ],
33
36
  "recursive": "true",
34
37
  "reporter": "mocha-multi-reporters",
35
38
  "reporter-options": "configFile=.mocha-multi.json",
@@ -50,7 +53,7 @@
50
53
  "hast-util-to-html": "8.0.4",
51
54
  "hast-util-to-string": "2.0.0",
52
55
  "hastscript": "7.2.0",
53
- "jose": "4.14.5",
56
+ "jose": "4.14.6",
54
57
  "mdast-util-to-hast": "12.3.0",
55
58
  "mdast-util-to-string": "4.0.0",
56
59
  "mime": "3.0.0",
@@ -73,18 +76,19 @@
73
76
  "@semantic-release/git": "10.0.1",
74
77
  "@semantic-release/npm": "10.0.5",
75
78
  "c8": "8.0.1",
76
- "eslint": "8.48.0",
79
+ "eslint": "8.49.0",
77
80
  "eslint-import-resolver-exports": "1.0.0-beta.5",
78
81
  "eslint-plugin-header": "3.1.1",
79
82
  "eslint-plugin-import": "2.28.1",
80
- "esmock": "2.3.8",
83
+ "esmock": "2.4.1",
81
84
  "husky": "8.0.3",
82
85
  "js-yaml": "4.1.0",
83
86
  "jsdom": "22.1.0",
84
87
  "junit-report-builder": "3.0.1",
85
- "lint-staged": "13.3.0",
88
+ "lint-staged": "14.0.1",
86
89
  "mocha": "10.2.0",
87
90
  "mocha-multi-reporters": "1.5.1",
91
+ "mocha-suppress-logs": "0.3.1",
88
92
  "semantic-release": "21.1.1"
89
93
  },
90
94
  "lint-staged": {
@@ -82,10 +82,7 @@ function readBlockConfig($block) {
82
82
  value = $img.properties.src;
83
83
  }
84
84
  }
85
- if (value) {
86
- // only keep non-empty value
87
- config[name] = value;
88
- }
85
+ config[name] = value;
89
86
  }
90
87
  }
91
88
  });
@@ -159,9 +156,20 @@ export default function extractMetaData(state, req) {
159
156
  const metaConfig = Object.assign(
160
157
  state.metadata.getModifiers(state.info.unmappedPath || state.info.path),
161
158
  state.mappedMetadata.getModifiers(state.info.unmappedPath),
162
- getLocalMetadata(hast),
163
159
  );
164
160
 
161
+ // prune empty values and explicit "" strings from sheet based metadata
162
+ Object.entries(metaConfig).forEach(([name, value]) => {
163
+ if (!value) {
164
+ delete metaConfig[name];
165
+ } else if (value === '""') {
166
+ metaConfig[name] = undefined;
167
+ }
168
+ });
169
+
170
+ // apply document local overrides
171
+ Object.assign(metaConfig, getLocalMetadata(hast));
172
+
165
173
  // first process supported metadata properties
166
174
  [
167
175
  'title',
@@ -172,15 +180,18 @@ export default function extractMetaData(state, req) {
172
180
  'image-alt',
173
181
  'canonical',
174
182
  'feed',
183
+ 'twitter:card',
175
184
  ].forEach((name) => {
176
- if (metaConfig[name]) {
185
+ if (name in metaConfig) {
177
186
  meta[name] = metaConfig[name];
178
187
  delete metaConfig[name];
179
188
  }
180
189
  });
181
- // default value for twitter:card (mandatory for rendering URLs as cards in tweets)
182
- meta['twitter:card'] = metaConfig['twitter:card'] || 'summary_large_image';
183
- delete metaConfig['twitter:card'];
190
+
191
+ if (!('twitter:card' in meta)) {
192
+ // default value for twitter:card (mandatory for rendering URLs as cards in tweets)
193
+ meta['twitter:card'] = 'summary_large_image';
194
+ }
184
195
 
185
196
  // add rest to meta.custom
186
197
  meta.custom = metaConfig;
@@ -193,7 +204,7 @@ export default function extractMetaData(state, req) {
193
204
  }
194
205
 
195
206
  // complete metadata with insights from content
196
- if (!meta.title) {
207
+ if (!('title' in meta)) {
197
208
  // content.title is not correct if the h1 is in a page-block since the pipeline
198
209
  // only respects the heading nodes in the mdast
199
210
  const $title = select('div h1', hast);
@@ -202,13 +213,13 @@ export default function extractMetaData(state, req) {
202
213
  }
203
214
  meta.title = content.title;
204
215
  }
205
- if (!meta.description) {
206
- meta.description = extractDescription(hast);
216
+ if (!('description' in meta)) {
217
+ meta.description = extractDescription(hast) || undefined;
207
218
  }
208
219
 
209
220
  // use the req.url and not the state.info.path in case of folder mapping
210
221
  meta.url = makeCanonicalHtmlUrl(getAbsoluteUrl(state, req.url.pathname));
211
- if (!meta.canonical) {
222
+ if (!('canonical' in meta)) {
212
223
  meta.canonical = meta.url;
213
224
  }
214
225
 
@@ -222,10 +233,12 @@ export default function extractMetaData(state, req) {
222
233
  }
223
234
  }
224
235
 
225
- meta.image = getAbsoluteUrl(
226
- state,
227
- optimizeMetaImage(state.info.path, meta.image || content.image || '/default-meta-image.png'),
228
- );
236
+ if (!('image' in meta)) {
237
+ meta.image = getAbsoluteUrl(
238
+ state,
239
+ optimizeMetaImage(state.info.path, meta.image || content.image || '/default-meta-image.png'),
240
+ );
241
+ }
229
242
 
230
- meta.imageAlt = meta['image-alt'] || content.imageAlt;
243
+ meta.imageAlt = meta['image-alt'] ?? content.imageAlt;
231
244
  }
@@ -26,7 +26,7 @@ function createElement(name, ...attrs) {
26
26
  const properties = {};
27
27
  for (let i = 0; i < attrs.length; i += 2) {
28
28
  const value = attrs[i + 1];
29
- if (!value) {
29
+ if (value === undefined) {
30
30
  return null;
31
31
  }
32
32
  properties[attrs[i]] = value;
@@ -50,9 +50,10 @@ export default async function render(state, req, res) {
50
50
  res.document = hast;
51
51
  return;
52
52
  }
53
- const $head = h('head', [
54
- h('title', meta.title),
55
- ]);
53
+ const $head = h('head');
54
+ if (meta.title !== undefined) {
55
+ $head.children.push(h('title', meta.title));
56
+ }
56
57
 
57
58
  // add meta
58
59
  // (this is so complicated to keep the order backward compatible to make the diff tests happy)
@@ -69,27 +70,37 @@ export default async function render(state, req, res) {
69
70
  'article:published_time': meta.published_time,
70
71
  'article:modified_time': meta.modified_time,
71
72
  'twitter:card': meta['twitter:card'],
72
- 'twitter:title': meta.title,
73
- 'twitter:description': meta.description,
74
- 'twitter:image': meta.image,
73
+ 'twitter:title': '',
74
+ 'twitter:description': '',
75
+ 'twitter:image': '',
75
76
  };
76
- // remove meta with no values
77
- for (const name of Object.keys(metadata)) {
78
- if (!metadata[name]) {
79
- delete metadata[name];
80
- }
81
- }
77
+
82
78
  // append custom metadata
83
79
  Object.assign(metadata, meta.custom);
84
80
 
85
- // remove og:url with explicit removal marker
86
- if (metadata['og:url'] === '""') {
87
- delete metadata['og:url'];
81
+ // fallback for twitter
82
+ const FALLBACKS = [
83
+ ['twitter:title', 'og:title'],
84
+ ['twitter:description', 'og:description'],
85
+ ['twitter:image', 'og:image'],
86
+ ];
87
+
88
+ for (const [from, to] of FALLBACKS) {
89
+ if (!(from in meta.custom)) {
90
+ metadata[from] = metadata[to];
91
+ }
92
+ }
93
+
94
+ // remove undefined metadata
95
+ for (const name of Object.keys(metadata)) {
96
+ if (metadata[name] === undefined) {
97
+ delete metadata[name];
98
+ }
88
99
  }
89
100
 
90
- appendElement($head, createElement('link', 'rel', 'canonical', 'href', content.meta.canonical));
91
- appendElement($head, createElement('meta', 'name', 'description', 'content', content.meta.description));
92
- appendElement($head, createElement('meta', 'name', 'keywords', 'content', content.meta.keywords));
101
+ appendElement($head, createElement('link', 'rel', 'canonical', 'href', meta.canonical));
102
+ appendElement($head, createElement('meta', 'name', 'description', 'content', meta.description));
103
+ appendElement($head, createElement('meta', 'name', 'keywords', 'content', meta.keywords));
93
104
 
94
105
  for (const [name, value] of Object.entries(metadata)) {
95
106
  const attr = name.includes(':') && !name.startsWith('twitter:') ? 'property' : 'name';