@redpanda-data/docs-extensions-and-macros 3.2.12 → 3.3.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/README.adoc CHANGED
@@ -77,6 +77,22 @@ antora:
77
77
  index-latest-only: true
78
78
  ```
79
79
 
80
+ === Component category aggregator
81
+
82
+ This extension maps Redpanda Connect component data into a structured format:
83
+
84
+ - Maps original component names to common names.
85
+ - Populates `connectCategoriesData` and `flatComponentsData` attributes.
86
+ - Skips deprecated components.
87
+
88
+ ==== Registration Example
89
+
90
+ ```yaml
91
+ antora:
92
+ extensions:
93
+ - require: '@redpanda-data/docs-extensions-and-macros/extensions/generate-rp-connect-categories'
94
+ ```
95
+
80
96
  === Version fetcher
81
97
 
82
98
  This extension fetches the latest release versions from GitHub.
@@ -420,6 +436,66 @@ asciidoc:
420
436
  - '@redpanda-data/docs-extensions-and-macros/macros/helm-ref'
421
437
  ----
422
438
 
439
+ === components_by_category
440
+
441
+ This macro generates a tabbed interface to display Redpanda Connect components by category.
442
+
443
+ The categories are fetched from the `connectCategoriesData` that's generated in the << Component category aggregator>> extension.
444
+
445
+ ==== Usage
446
+
447
+ ```asciidoc
448
+ components_by_category::[<type>]
449
+ ```
450
+
451
+ ==== Registration example
452
+
453
+ ```yaml
454
+ asciidoc:
455
+ extensions:
456
+ - '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
457
+ ```
458
+
459
+ === component_table
460
+
461
+ This macro generates a searchable table of all Redpanda Connect components with filters for support and type.
462
+
463
+ The types are fetched from the `flatComponentsData` that's generated in the << Component category aggregator>> extension.
464
+
465
+ ==== Usage
466
+
467
+ ```asciidoc
468
+ component_table::[]
469
+ ```
470
+
471
+ ==== Registration example
472
+
473
+ ```yaml
474
+ asciidoc:
475
+ extensions:
476
+ - '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
477
+ ```
478
+
479
+ === component_type_dropdown
480
+
481
+ This macro generates a dropdown of other supported types for a particular component, allowing users to switch between different types.
482
+
483
+ The types are fetched from the `flatComponentsData` that's generated in the << Component category aggregator>> extension.
484
+
485
+ ==== Usage
486
+
487
+ ```asciidoc
488
+ component_type_dropdown::[]
489
+ ```
490
+
491
+ ==== Registration example
492
+
493
+ ```yaml
494
+ asciidoc:
495
+ extensions:
496
+ - '@redpanda-data/docs-extensions-and-macros/macros/rp-connect-components'
497
+ ```
498
+
423
499
  == Development quickstart
424
500
 
425
501
  This section provides information on how to develop this project.
@@ -0,0 +1,159 @@
1
+ 'use strict';
2
+
3
+ module.exports.register = function ({ config }) {
4
+ const logger = this.getLogger('redpanda-connect-category-aggregation-extension');
5
+
6
+ // TODO: Integrate these as attributes like common-name in the autogenerated source pages.
7
+ // This hardcoded map is just for launch.
8
+ const componentNameMap = {
9
+ "aws_kinesis_data_streams": "AWS Kinesis Data Streams",
10
+ "aws_kinesis_firehose": "AWS Kinesis Firehose",
11
+ "aws_kinesis": "AWS Kinesis",
12
+ "aws_sqs": "AWS SQS",
13
+ "aws_sns": "AWS SNS",
14
+ "azure_cosmosdb": "Azure Cosmos DB",
15
+ "azure_table_storage": "Azure Table Storage",
16
+ "gcp_bigquery": "GCP BigQuery",
17
+ "oracle": "Oracle",
18
+ "snowflake_put": "Snowflake",
19
+ "aws_dynamodb": "AWS DynamoDB",
20
+ "azure_blob_storage": "Azure Blob Storage",
21
+ "aws_s3": "AWS S3",
22
+ "cassandra": "Cassandra",
23
+ "gcp_cloud_storage": "GCP Cloud Storage",
24
+ "amqp": "AMQP",
25
+ "avro": "Avro",
26
+ "awk": "AWK",
27
+ "aws_lambda": "AWS Lambda",
28
+ "azure_queue_storage": "Azure Queue Storage",
29
+ "clickhouse": "ClickHouse",
30
+ "cockroachdb_changefeed": "CockroachDB",
31
+ "couchbase": "Couchbase",
32
+ "csv": "CSV",
33
+ "discord": "Discord",
34
+ "elasticsearch": "Elasticsearch",
35
+ "kafka_franz": "Franz-go",
36
+ "gcp_pubsub": "GCP Pub/Sub",
37
+ "grok": "Grok",
38
+ "hdfs": "HDFS",
39
+ "http": "HTTP",
40
+ "javascript": "JavaScript",
41
+ "jmespath": "JMESPath",
42
+ "json_schema": "JSON Schema",
43
+ "kafka": "Kafka",
44
+ "memcached": "Memcached",
45
+ "msgpack": "MessagePack",
46
+ "mongodb": "MongoDB",
47
+ "mqtt": "MQTT",
48
+ "mysql": "MySQL",
49
+ "nanomsg": "nanomsg",
50
+ "nats": "NATS",
51
+ "nsq": "NSQ",
52
+ "opensearch": "OpenSearch",
53
+ "parquet": "Parquet",
54
+ "postgresql": "PostgreSQL",
55
+ "protobuf": "Protobuf",
56
+ "pulsar": "Pulsar",
57
+ "redis": "Redis",
58
+ "ristretto": "Ristretto",
59
+ "schema_registry": "Schema Registry",
60
+ "sentry": "Sentry",
61
+ "socket": "Socket",
62
+ "sql": "SQL",
63
+ "sqlite": "SQLite",
64
+ "trino": "Trino",
65
+ "wasm": "Wasm (WebAssembly)",
66
+ "websocket": "WebSocket",
67
+ "twitter_search": "X (Twitter)",
68
+ "xml": "XML"
69
+ };
70
+
71
+ this.once('contentClassified', ({ siteCatalog, contentCatalog }) => {
72
+ const redpandaConnect = contentCatalog.getComponents().find(component => component.name === 'redpanda-connect');
73
+ if (!redpandaConnect || !redpandaConnect.latest) {
74
+ logger.info('Could not find the redpanda-connect component');
75
+ return;
76
+ }
77
+ const descriptions = redpandaConnect.latest.asciidoc.attributes.categories;
78
+ if (!descriptions) {
79
+ logger.info('No categories attribute found in redpanda-connect component');
80
+ return;
81
+ }
82
+
83
+ const connectCategoriesData = {};
84
+ const flatComponentsData = [];
85
+ const types = Object.keys(descriptions);
86
+
87
+ // Initialize connectCategoriesData for each type
88
+ types.forEach(type => {
89
+ connectCategoriesData[type] = [];
90
+ });
91
+
92
+ try {
93
+ const files = contentCatalog.findBy({ component: 'redpanda-connect', family: 'page' });
94
+
95
+ files.forEach(file => {
96
+ const content = file.contents.toString('utf8');
97
+ const categoryMatch = /:categories: (.*)/.exec(content);
98
+ const typeMatch = /:type: (.*)/.exec(content);
99
+ const statusMatch = /:status: (.*)/.exec(content);
100
+ const pubUrl = file.pub.url;
101
+ const name = file.src.stem;
102
+
103
+ if (categoryMatch && typeMatch) {
104
+ let categories = categoryMatch[1];
105
+ const fileType = typeMatch[1];
106
+ let status = statusMatch ? statusMatch[1] : 'Enterprise';
107
+ if (status === 'beta' || status === 'experimental') status = 'Community';
108
+ if (status === 'stable') status = 'Enterprise';
109
+
110
+ // Skip deprecated components
111
+ if (status === 'deprecated') return;
112
+
113
+ // Map component name to common name
114
+ const commonName = componentNameMap[name] || name;
115
+
116
+ // Populate connectCategoriesData
117
+ if (types.includes(fileType) && categories) {
118
+ if (typeof categories === 'string') {
119
+ categories = categories.replace(/[\[\]"]/g, '').split(',').map(category => category.trim());
120
+ }
121
+
122
+ categories.forEach(category => {
123
+ let categoryObj = connectCategoriesData[fileType].find(cat => cat.name === category);
124
+
125
+ if (!categoryObj) {
126
+ categoryObj = descriptions[fileType].find(desc => desc.name === category) || { name: category, description: "" };
127
+ categoryObj.items = [];
128
+ connectCategoriesData[fileType].push(categoryObj);
129
+ }
130
+
131
+ categoryObj.items.push({ name: commonName, url: pubUrl, status: status });
132
+ });
133
+ }
134
+
135
+ // Populate flatComponentsData
136
+ let flatItem = flatComponentsData.find(item => item.name === commonName);
137
+ if (flatItem) {
138
+ if (!flatItem.types.includes(fileType)) {
139
+ flatItem.types.push({ type: fileType, url: pubUrl });
140
+ }
141
+ } else {
142
+ flatItem = { name: commonName, originalName: name, support: status, types: [{ type: fileType, url: pubUrl }] };
143
+ flatComponentsData.push(flatItem);
144
+ }
145
+ }
146
+ });
147
+
148
+ try {
149
+ redpandaConnect.latest.asciidoc.attributes.connectCategoriesData = connectCategoriesData;
150
+ redpandaConnect.latest.asciidoc.attributes.flatComponentsData = flatComponentsData;
151
+ logger.info(`Added Redpanda Connect data to latest Asciidoc object: ${JSON.stringify({ connectCategoriesData, flatComponentsData }, null, 2)}`);
152
+ } catch (error) {
153
+ logger.error(`Error updating latest Asciidoc object for Redpanda Connect: ${error.message}`);
154
+ }
155
+ } catch (error) {
156
+ logger.error(`Error processing Redpanda Connect files: ${error.message}`);
157
+ }
158
+ });
159
+ };
@@ -2,7 +2,7 @@ module.exports.register = function ({ config }) {
2
2
  const { addToNavigation, unlistedPagesHeading = 'Unlisted Pages' } = config
3
3
  const logger = this.getLogger('unlisted-pages-extension')
4
4
  this
5
- .on('navigationBuilt', ({ contentCatalog }) => {
5
+ .on('navigationBuilt', ({ navigationCatalog, contentCatalog }) => {
6
6
  contentCatalog.getComponents().forEach(({ versions }) => {
7
7
  versions.forEach(({ name: component, version, navigation: nav, url: defaultUrl }) => {
8
8
  if (component === 'api') return;
@@ -16,7 +16,10 @@ module.exports.register = function ({ config }) {
16
16
  logger.warn({ file: page.src, source: page.src.origin }, 'detected unlisted page')
17
17
  return collector.concat(page)
18
18
  }, [])
19
- if (unlistedPages.length && addToNavigation) {
19
+ if (unlistedPages.length && component === 'redpanda-connect') {
20
+ // Some component pages for Redpanda Connect are autogenerated. This function tries to add unlisted component pages to the nav in case a new one gets created without updating the nav.
21
+ addRedpandaConnectPagesToNav(nav[0].items, unlistedPages)
22
+ } else if (unlistedPages.length && addToNavigation) {
20
23
  nav.push({
21
24
  content: unlistedPagesHeading,
22
25
  items: unlistedPages.map((page) => {
@@ -36,4 +39,31 @@ function getNavEntriesByUrl (items = [], accum = {}) {
36
39
  getNavEntriesByUrl(item.items, accum)
37
40
  })
38
41
  return accum
42
+ }
43
+
44
+ function addRedpandaConnectPagesToNav(navItems, pages) {
45
+ // get the Components nav section
46
+ let componentsSection = navItems.find(item => item.content === 'Components');
47
+ if (!componentsSection) return
48
+
49
+ pages.forEach(page => {
50
+ const dirname = page.out.dirname;
51
+ if (!dirname.includes('/components/')) return
52
+ const heading = page.asciidoc.attributes.doctitle;
53
+ const pathParts = dirname.split('/').slice(2); // Get the type
54
+ // get existing nav items inside the Components tree
55
+ let currentLevel = componentsSection.items;
56
+
57
+ pathParts.forEach((part, index) => {
58
+ const capitalizedPart = part.charAt(0).toUpperCase() + part.slice(1);
59
+ let section = currentLevel.find(item => item.content === capitalizedPart);
60
+ if (!section) {
61
+ section = { content: capitalizedPart, items: [], root: index === 0 };
62
+ currentLevel.push(section);
63
+ }
64
+ currentLevel = section.items;
65
+ });
66
+
67
+ currentLevel.push({ content: page.asciidoc.navtitle || page.src.stem, url: page.pub.url, urlType: 'internal' });
68
+ });
39
69
  }
@@ -0,0 +1,248 @@
1
+ 'use strict';
2
+
3
+ module.exports.register = function (registry, context) {
4
+ let tabsCounter = 1; // Counter for generating unique IDs
5
+
6
+ // Add the category tabs for components
7
+ registry.blockMacro(function () {
8
+ const self = this;
9
+ self.named('components_by_category');
10
+ self.positionalAttributes(['type']);
11
+ self.process((parent, target, attrs) => {
12
+ const type = attrs.type;
13
+ const categoriesData = context.config?.attributes?.connectCategoriesData || {}
14
+ const categories = categoriesData[type] || {};
15
+ const currentTabsId = `tabs-${tabsCounter++}`; // Unique ID for this set of tabs
16
+
17
+ let tabsHtml = `
18
+ <div id="${currentTabsId}" class="openblock tabs is-sync is-loaded" data-sync-group-id="${type}">
19
+ <div class="content">
20
+ <div class="ulist tablist">
21
+ <ul role="tablist">`;
22
+
23
+ categories.forEach((category, index) => {
24
+ tabsHtml += `
25
+ <li id="${currentTabsId}-${category.name}" class="tab" tabindex="${index === 0 ? '0' : '-1'}" role="tab" data-sync-id="${category.name}" aria-controls="${currentTabsId}-${category.name}--panel" aria-selected="${index === 0}">
26
+ <p>${category.name}</p>
27
+ </li>`;
28
+ });
29
+
30
+ tabsHtml += `
31
+ </ul>
32
+ </div>`;
33
+
34
+ categories.forEach((category, index) => {
35
+ tabsHtml += `
36
+ <div id="${currentTabsId}-${category.name}--panel" class="tabpanel${index === 0 ? '' : ' is-hidden'}" aria-labelledby="${currentTabsId}-${category.name}"${index === 0 ? '' : ' hidden'} role="tabpanel">
37
+ <div class="listingblock">
38
+ <div class="content">
39
+ <p>${category.description}</p>
40
+ <div class="two-column-grid">`;
41
+ category.items.forEach(item => {
42
+ tabsHtml += `
43
+ <a href="${item.url}" class="component-card"><strong>${item.name}</strong></a>`;
44
+ });
45
+ tabsHtml += `
46
+ </div>
47
+ </div>
48
+ </div>
49
+ </div>`;
50
+ });
51
+
52
+ tabsHtml += `
53
+ </div>
54
+ </div>`;
55
+
56
+ return self.createBlock(parent, 'pass', tabsHtml);
57
+ });
58
+ });
59
+
60
+ // Add the searchable table of all components (component catalog)
61
+ registry.blockMacro(function () {
62
+ const self = this;
63
+ self.named('component_table');
64
+ self.process((parent, target, attrs) => {
65
+ const flatComponentsData = context.config?.attributes?.flatComponentsData || []
66
+
67
+ // Sort flatComponentsData alphabetically by name
68
+ flatComponentsData.sort((a, b) => a.name.localeCompare(b.name));
69
+
70
+ let tableHtml = `
71
+ <div class="table-filters">
72
+ <input class="table-search" type="text" id="componentTableSearch" onkeyup="filterComponentTable()" placeholder="Search for components...">
73
+ <select class="type-dropdown" id="supportFilter" onchange="filterComponentTable()">
74
+ <option value="">All Support</option>`;
75
+
76
+ // Extract unique support values for the filter
77
+ const uniqueSupportValues = [...new Set(flatComponentsData.map(item => item.support))];
78
+ uniqueSupportValues.forEach(support => {
79
+ tableHtml += `<option value="${support}">${support}</option>`;
80
+ });
81
+
82
+ tableHtml += `
83
+ </select>
84
+ <select class="type-dropdown" id="typeFilter" onchange="filterComponentTable()">
85
+ <option value="">All Types</option>`;
86
+
87
+ // Extract unique types for the filter
88
+ const uniqueTypes = [...new Set(flatComponentsData.flatMap(item => item.types.map(typeObj => typeObj.type)))];
89
+ uniqueTypes.forEach(type => {
90
+ tableHtml += `<option value="${type}">${type.charAt(0).toUpperCase() + type.slice(1)}</option>`;
91
+ });
92
+
93
+ tableHtml += `
94
+ </select>
95
+ </div>
96
+ <table class="tableblock frame-all grid-all stripes-even no-clip stretch component-table" id="componentTable">
97
+ <colgroup>
98
+ <col style="width: 33.3%;">
99
+ <col style="width: 33.3%;">
100
+ <col style="width: 33.3%;">
101
+ </colgroup>
102
+ <thead>
103
+ <tr>
104
+ <th class="tableblock halign-left valign-top">Connector</th>
105
+ <th class="tableblock halign-left valign-top">Support</th>
106
+ <th class="tableblock halign-left valign-top">Type</th>
107
+ </tr>
108
+ </thead>
109
+ <tbody>`;
110
+
111
+ flatComponentsData.forEach(item => {
112
+ const typeDropdown = item.types.length > 1
113
+ ? `<select class="type-dropdown" onchange="updateComponentUrl(this, true)">
114
+ ${item.types.map(typeObj => `<option value="${typeObj.url}">${typeObj.type.charAt(0).toUpperCase() + typeObj.type.slice(1)}</option>`).join('')}
115
+ </select>`
116
+ : item.types[0].type;
117
+
118
+ tableHtml += `
119
+ <tr>
120
+ <td class="tableblock halign-left valign-top"><p class="tableblock"><a href="${item.types[0].url}">${item.name}</a></p></td>
121
+ <td class="tableblock halign-left valign-top"><p class="tableblock">${item.support}</p></td>
122
+ <td class="tableblock halign-left valign-top"><p class="tableblock">${typeDropdown}</p></td>
123
+ </tr>`;
124
+ });
125
+
126
+ tableHtml += `
127
+ </tbody>
128
+ </table>
129
+ <script>
130
+ function filterComponentTable() {
131
+ const nameInput = document.getElementById('componentTableSearch').value.toLowerCase();
132
+ const supportFilter = document.getElementById('supportFilter').value;
133
+ const typeFilter = document.getElementById('typeFilter').value;
134
+ const table = document.getElementById('componentTable');
135
+ const trs = table.getElementsByTagName('tr');
136
+
137
+ for (let i = 1; i < trs.length; i++) {
138
+ const nameTd = trs[i].getElementsByTagName('td')[0];
139
+ const supportTd = trs[i].getElementsByTagName('td')[1];
140
+ const typeTd = trs[i].getElementsByTagName('td')[2];
141
+ const typeDropdown = typeTd.querySelector('.type-dropdown');
142
+ let showRow =
143
+ (!nameInput || nameTd.textContent.toLowerCase().includes(nameInput)) &&
144
+ (!supportFilter || supportTd.textContent === supportFilter) &&
145
+ (!typeFilter || (typeDropdown ? Array.from(typeDropdown.options).some(option => option.text.toLowerCase() === typeFilter.toLowerCase()) : typeTd.textContent.toLowerCase().includes(typeFilter.toLowerCase())));
146
+
147
+ trs[i].style.display = showRow ? '' : 'none';
148
+
149
+ if (showRow && typeFilter && typeDropdown) {
150
+ const matchingOption = Array.from(typeDropdown.options).find(option => option.text.toLowerCase() === typeFilter.toLowerCase());
151
+ typeDropdown.value = matchingOption.value;
152
+ updateComponentUrl(typeDropdown, false);
153
+ }
154
+ }
155
+ }
156
+ function getQueryParams() {
157
+ const params = {};
158
+ const searchParams = new URLSearchParams(window.location.search);
159
+ searchParams.forEach((value, key) => {
160
+ params[key] = value;
161
+ });
162
+ return params;
163
+ }
164
+
165
+ function capitalizeFirstLetter(string) {
166
+ return string.charAt(0).toUpperCase() + string.slice(1);
167
+ }
168
+
169
+ function updateComponentUrl(select, redirect) {
170
+ const anchor = select.closest('tr').querySelector('a');
171
+ anchor.href = select.value;
172
+ if (redirect) {
173
+ window.location.href = select.value; // Redirect to the new URL
174
+ }
175
+ }
176
+ // Initialize Choices.js for type dropdowns
177
+ document.addEventListener('DOMContentLoaded', function() {
178
+ const params = getQueryParams();
179
+ if (params.search) {
180
+ document.getElementById('componentTableSearch').value = params.search;
181
+ }
182
+ if (params.support) {
183
+ document.getElementById('supportFilter').value = capitalizeFirstLetter(params.support);
184
+ }
185
+ if (params.type) {
186
+ document.getElementById('typeFilter').value = params.type;
187
+ }
188
+ filterComponentTable();
189
+ const typeDropdowns = document.querySelectorAll('.type-dropdown');
190
+ typeDropdowns.forEach(dropdown => {
191
+ new Choices(dropdown, { searchEnabled: false, allowHTML: true});
192
+ });
193
+ });
194
+ </script>`;
195
+
196
+ return self.createBlock(parent, 'pass', tableHtml);
197
+ });
198
+ });
199
+ // Add the block macro for displaying a dropdown of other supported types
200
+ registry.blockMacro(function () {
201
+ const self = this;
202
+ self.named('component_type_dropdown');
203
+ self.process((parent, target, attrs) => {
204
+ const attributes = parent.getDocument().getAttributes();
205
+ const name = attributes['doctitle'];
206
+ const type = attributes['type'];
207
+
208
+ if (!name || !type) {
209
+ console.log('Name or type attribute is missing');
210
+ return self.createBlock(parent, 'pass', '');
211
+ }
212
+
213
+ const flatComponentsData = context.config?.attributes?.flatComponentsData || []
214
+ const component = flatComponentsData.find(item => item.originalName === name);
215
+
216
+ if (!component || component.types.length <= 1) {
217
+ return self.createBlock(parent, 'pass', '');
218
+ }
219
+
220
+ // Move the page's current type to the first position in the dropdown
221
+ const sortedTypes = [...component.types];
222
+ const currentTypeIndex = sortedTypes.findIndex(typeObj => typeObj.type === type);
223
+ if (currentTypeIndex !== -1) {
224
+ const [currentType] = sortedTypes.splice(currentTypeIndex, 1);
225
+ sortedTypes.unshift(currentType);
226
+ }
227
+
228
+ const typeDropdown = `
229
+ <div class="page-type-dropdown">
230
+ <p>Type: </p>
231
+ <select class="type-dropdown" onchange="window.location.href=this.value">
232
+ ${sortedTypes.map(typeObj => `<option value="${typeObj.url}" data-support="${typeObj.support}">${typeObj.type.charAt(0).toUpperCase() + typeObj.type.slice(1)}</option>`).join('')}
233
+ </select>
234
+ </div>
235
+ <script>
236
+ // Initialize Choices.js for type dropdowns
237
+ document.addEventListener('DOMContentLoaded', function() {
238
+ const typeDropdowns = document.querySelectorAll('.type-dropdown');
239
+ typeDropdowns.forEach(dropdown => {
240
+ new Choices(dropdown, { searchEnabled: false, allowHTML: true, shouldSort: false });
241
+ });
242
+ });
243
+ </script>`;
244
+
245
+ return self.createBlock(parent, 'pass', typeDropdown);
246
+ });
247
+ });
248
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@redpanda-data/docs-extensions-and-macros",
3
- "version": "3.2.12",
3
+ "version": "3.3.1",
4
4
  "description": "Antora extensions and macros developed for Redpanda documentation.",
5
5
  "keywords": [
6
6
  "antora",
@@ -30,6 +30,7 @@
30
30
  "require": "./extensions/unlisted-pages.js"
31
31
  },
32
32
  "./extensions/replace-attributes-in-attachments": "./extensions/replace-attributes-in-attachments.js",
33
+ "./extensions/generate-rp-connect-categories": "./extensions/generate-rp-connect-categories.js",
33
34
  "./extensions/add-global-attributes": "./extensions/add-global-attributes.js",
34
35
  "./extensions/version-fetcher/set-latest-version": "./extensions/version-fetcher/set-latest-version.js",
35
36
  "./extensions/validate-attributes": "./extensions/validate-attributes.js",
@@ -40,6 +41,7 @@
40
41
  "./extensions/algolia-indexer/index": "./extensions/algolia-indexer/index.js",
41
42
  "./extensions/aggregate-terms": "./extensions/aggregate-terms.js",
42
43
  "./macros/glossary": "./macros/glossary.js",
44
+ "./macros/rp-connect-components": "./macros/rp-connect-components.js",
43
45
  "./macros/config-ref": "./macros/config-ref.js",
44
46
  "./macros/helm-ref": "./macros/helm-ref.js"
45
47
  },