@adobe/spacecat-shared-tokowaka-client 1.4.6 → 1.4.7
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 +7 -0
- package/README.md +14 -2
- package/package.json +1 -1
- package/src/index.js +47 -1
- package/test/index.test.js +155 -0
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
# [@adobe/spacecat-shared-tokowaka-client-v1.4.7](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.4.6...@adobe/spacecat-shared-tokowaka-client-v1.4.7) (2026-01-14)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* update tokowaka s3 config with patches ([#1263](https://github.com/adobe/spacecat-shared/issues/1263)) ([59605e9](https://github.com/adobe/spacecat-shared/commit/59605e9b75b7289029bd14e1832e594ea565296d))
|
|
7
|
+
|
|
1
8
|
# [@adobe/spacecat-shared-tokowaka-client-v1.4.6](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.4.5...@adobe/spacecat-shared-tokowaka-client-v1.4.6) (2026-01-13)
|
|
2
9
|
|
|
3
10
|
|
package/README.md
CHANGED
|
@@ -64,10 +64,12 @@ Creates a client instance from a context object.
|
|
|
64
64
|
|
|
65
65
|
#### `deploySuggestions(site, opportunity, suggestions)`
|
|
66
66
|
|
|
67
|
-
Generates configuration and uploads to S3 **per URL**. **Automatically fetches existing configuration for each URL and merges** new suggestions with it. Invalidates CDN cache after upload.
|
|
67
|
+
Generates configuration and uploads to S3 **per URL**. **Automatically fetches existing configuration for each URL and merges** new suggestions with it. Invalidates CDN cache after upload. **Updates the metaconfig's `patches` field** to track deployed endpoints.
|
|
68
68
|
|
|
69
69
|
**Architecture Change:** Creates one S3 file per URL instead of a single file with all URLs. This prevents files from growing too large over time.
|
|
70
70
|
|
|
71
|
+
**Metaconfig Tracking:** After successful deployment, the method updates the domain-level metaconfig's `patches` object with the normalized paths of all deployed endpoints (e.g., `{ "/products/item": true }`). This provides a centralized registry of all deployed endpoints for the domain.
|
|
72
|
+
|
|
71
73
|
**Returns:** `Promise<DeploymentResult>` with:
|
|
72
74
|
- `s3Paths` - Array of S3 keys where configs were uploaded (one per URL)
|
|
73
75
|
- `cdnInvalidations` - Array of CDN invalidation results (one per URL per provider)
|
|
@@ -212,7 +214,17 @@ Domain-level metaconfig (created once per domain, shared by all URLs):
|
|
|
212
214
|
{
|
|
213
215
|
"siteId": "abc-123",
|
|
214
216
|
"apiKeys": ["tokowaka-api-key-1"],
|
|
215
|
-
"
|
|
217
|
+
"tokowakaEnabled": true,
|
|
218
|
+
"enhancements": true,
|
|
219
|
+
"prerender": {
|
|
220
|
+
"allowList": [],
|
|
221
|
+
"denyList": ["/*"]
|
|
222
|
+
},
|
|
223
|
+
"patches": {
|
|
224
|
+
"/products/item": true,
|
|
225
|
+
"/about": true,
|
|
226
|
+
"/contact": true
|
|
227
|
+
}
|
|
216
228
|
}
|
|
217
229
|
```
|
|
218
230
|
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -17,7 +17,12 @@ import { v4 as uuidv4 } from 'uuid';
|
|
|
17
17
|
import MapperRegistry from './mappers/mapper-registry.js';
|
|
18
18
|
import CdnClientRegistry from './cdn/cdn-client-registry.js';
|
|
19
19
|
import { mergePatches } from './utils/patch-utils.js';
|
|
20
|
-
import {
|
|
20
|
+
import {
|
|
21
|
+
getTokowakaConfigS3Path,
|
|
22
|
+
getTokowakaMetaconfigS3Path,
|
|
23
|
+
getHostName,
|
|
24
|
+
normalizePath,
|
|
25
|
+
} from './utils/s3-utils.js';
|
|
21
26
|
import { groupSuggestionsByUrlPath, filterEligibleSuggestions } from './utils/suggestion-utils.js';
|
|
22
27
|
import { getEffectiveBaseURL } from './utils/site-utils.js';
|
|
23
28
|
import { fetchHtmlWithWarmup, calculateForwardedHost } from './utils/custom-html-utils.js';
|
|
@@ -94,6 +99,44 @@ class TokowakaClient {
|
|
|
94
99
|
return error;
|
|
95
100
|
}
|
|
96
101
|
|
|
102
|
+
/**
|
|
103
|
+
* Updates the metaconfig with deployed endpoint paths
|
|
104
|
+
* @param {Object} metaconfig - Existing metaconfig object
|
|
105
|
+
* @param {Array<string>} deployedUrls - Array of successfully deployed URLs
|
|
106
|
+
* @param {string} baseUrl - Base URL for uploading metaconfig
|
|
107
|
+
* @returns {Promise<void>}
|
|
108
|
+
* @private
|
|
109
|
+
*/
|
|
110
|
+
async #updateMetaconfigWithDeployedPaths(metaconfig, deployedUrls, baseUrl) {
|
|
111
|
+
if (!Array.isArray(deployedUrls) || deployedUrls.length === 0) {
|
|
112
|
+
return;
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
try {
|
|
116
|
+
// Initialize patches field if it doesn't exist
|
|
117
|
+
const updatedMetaconfig = {
|
|
118
|
+
...metaconfig,
|
|
119
|
+
patches: { ...(metaconfig.patches || {}) },
|
|
120
|
+
};
|
|
121
|
+
|
|
122
|
+
// Extract normalized paths from deployed URLs and add to patches object
|
|
123
|
+
deployedUrls.forEach((url) => {
|
|
124
|
+
const urlObj = new URL(url);
|
|
125
|
+
const normalizedPath = normalizePath(urlObj.pathname);
|
|
126
|
+
updatedMetaconfig.patches[normalizedPath] = true;
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
await this.uploadMetaconfig(baseUrl, updatedMetaconfig);
|
|
130
|
+
this.log.info(`Updated metaconfig with ${deployedUrls.length} deployed endpoint(s)`);
|
|
131
|
+
} catch (error) {
|
|
132
|
+
this.log.error(`Failed to update metaconfig with deployed paths: ${error.message}`, error);
|
|
133
|
+
throw this.#createError(
|
|
134
|
+
`Failed to update metaconfig with deployed paths: ${error.message}`,
|
|
135
|
+
HTTP_INTERNAL_SERVER_ERROR,
|
|
136
|
+
);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
97
140
|
/**
|
|
98
141
|
* Gets the list of CDN providers from environment configuration
|
|
99
142
|
* Supports both single provider (string) and multiple providers (comma-separated string or array)
|
|
@@ -597,6 +640,9 @@ class TokowakaClient {
|
|
|
597
640
|
|
|
598
641
|
this.log.info(`Uploaded Tokowaka configs for ${s3Paths.length} URLs`);
|
|
599
642
|
|
|
643
|
+
// Update metaconfig with deployed paths
|
|
644
|
+
await this.#updateMetaconfigWithDeployedPaths(metaconfig, deployedUrls, baseURL);
|
|
645
|
+
|
|
600
646
|
// Invalidate CDN cache for all deployed URLs at once
|
|
601
647
|
const cdnInvalidations = await this.invalidateCdnCache({ urls: deployedUrls });
|
|
602
648
|
|
package/test/index.test.js
CHANGED
|
@@ -1030,6 +1030,161 @@ describe('TokowakaClient', () => {
|
|
|
1030
1030
|
expect(result.failedSuggestions[0].suggestion.getId()).to.equal('sugg-2');
|
|
1031
1031
|
});
|
|
1032
1032
|
|
|
1033
|
+
it('should update metaconfig patches field with deployed endpoints', async () => {
|
|
1034
|
+
mockSuggestions = [
|
|
1035
|
+
{
|
|
1036
|
+
getId: () => 'sugg-1',
|
|
1037
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1038
|
+
getData: () => ({
|
|
1039
|
+
url: 'https://example.com/page1',
|
|
1040
|
+
recommendedAction: 'Page 1 Heading',
|
|
1041
|
+
checkType: 'heading-empty',
|
|
1042
|
+
transformRules: {
|
|
1043
|
+
action: 'replace',
|
|
1044
|
+
selector: 'h1',
|
|
1045
|
+
},
|
|
1046
|
+
}),
|
|
1047
|
+
},
|
|
1048
|
+
{
|
|
1049
|
+
getId: () => 'sugg-2',
|
|
1050
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1051
|
+
getData: () => ({
|
|
1052
|
+
url: 'https://example.com/page2',
|
|
1053
|
+
recommendedAction: 'Page 2 Heading',
|
|
1054
|
+
checkType: 'heading-empty',
|
|
1055
|
+
transformRules: {
|
|
1056
|
+
action: 'replace',
|
|
1057
|
+
selector: 'h1',
|
|
1058
|
+
},
|
|
1059
|
+
}),
|
|
1060
|
+
},
|
|
1061
|
+
];
|
|
1062
|
+
|
|
1063
|
+
const result = await client.deploySuggestions(
|
|
1064
|
+
mockSite,
|
|
1065
|
+
mockOpportunity,
|
|
1066
|
+
mockSuggestions,
|
|
1067
|
+
);
|
|
1068
|
+
|
|
1069
|
+
expect(result.succeededSuggestions).to.have.length(2);
|
|
1070
|
+
// Verify uploadMetaconfig was called to update the metaconfig
|
|
1071
|
+
expect(client.uploadMetaconfig).to.have.been.called;
|
|
1072
|
+
// Check that the last call included the patches field
|
|
1073
|
+
const { lastCall } = client.uploadMetaconfig;
|
|
1074
|
+
expect(lastCall.args[1]).to.have.property('patches');
|
|
1075
|
+
expect(lastCall.args[1].patches).to.deep.equal({
|
|
1076
|
+
'/page1': true,
|
|
1077
|
+
'/page2': true,
|
|
1078
|
+
});
|
|
1079
|
+
});
|
|
1080
|
+
|
|
1081
|
+
it('should add to existing patches in metaconfig when deploying new endpoints', async () => {
|
|
1082
|
+
// Set up metaconfig with existing patches
|
|
1083
|
+
// Reset the stub to provide consistent behavior
|
|
1084
|
+
client.fetchMetaconfig.reset();
|
|
1085
|
+
client.fetchMetaconfig.resolves({
|
|
1086
|
+
siteId: 'site-123',
|
|
1087
|
+
prerender: true,
|
|
1088
|
+
patches: {
|
|
1089
|
+
'/existing-page': true,
|
|
1090
|
+
},
|
|
1091
|
+
});
|
|
1092
|
+
|
|
1093
|
+
mockSuggestions = [
|
|
1094
|
+
{
|
|
1095
|
+
getId: () => 'sugg-1',
|
|
1096
|
+
getUpdatedAt: () => '2025-01-15T10:00:00.000Z',
|
|
1097
|
+
getData: () => ({
|
|
1098
|
+
url: 'https://example.com/new-page',
|
|
1099
|
+
recommendedAction: 'New Heading',
|
|
1100
|
+
checkType: 'heading-empty',
|
|
1101
|
+
transformRules: {
|
|
1102
|
+
action: 'replace',
|
|
1103
|
+
selector: 'h1',
|
|
1104
|
+
},
|
|
1105
|
+
}),
|
|
1106
|
+
},
|
|
1107
|
+
];
|
|
1108
|
+
|
|
1109
|
+
await client.deploySuggestions(
|
|
1110
|
+
mockSite,
|
|
1111
|
+
mockOpportunity,
|
|
1112
|
+
mockSuggestions,
|
|
1113
|
+
);
|
|
1114
|
+
|
|
1115
|
+
// Verify the updated metaconfig includes both existing and new patches
|
|
1116
|
+
const { lastCall } = client.uploadMetaconfig;
|
|
1117
|
+
expect(lastCall.args[1].patches).to.deep.equal({
|
|
1118
|
+
'/existing-page': true,
|
|
1119
|
+
'/new-page': true,
|
|
1120
|
+
});
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
it('should throw error when metaconfig update fails', async () => {
|
|
1124
|
+
// Make uploadMetaconfig fail during the update
|
|
1125
|
+
client.uploadMetaconfig.rejects(new Error('S3 upload error'));
|
|
1126
|
+
|
|
1127
|
+
try {
|
|
1128
|
+
await client.deploySuggestions(
|
|
1129
|
+
mockSite,
|
|
1130
|
+
mockOpportunity,
|
|
1131
|
+
mockSuggestions,
|
|
1132
|
+
);
|
|
1133
|
+
expect.fail('Should have thrown error');
|
|
1134
|
+
} catch (error) {
|
|
1135
|
+
expect(error.message).to.include('Failed to update metaconfig with deployed paths');
|
|
1136
|
+
expect(error.status).to.equal(500);
|
|
1137
|
+
}
|
|
1138
|
+
});
|
|
1139
|
+
|
|
1140
|
+
it('should return early when no eligible suggestions to deploy', async () => {
|
|
1141
|
+
// All suggestions are ineligible
|
|
1142
|
+
mockSuggestions = [
|
|
1143
|
+
{
|
|
1144
|
+
getId: () => 'sugg-1',
|
|
1145
|
+
getData: () => ({
|
|
1146
|
+
url: 'https://example.com/page1',
|
|
1147
|
+
recommendedAction: 'New Heading',
|
|
1148
|
+
checkType: 'heading-missing', // Not eligible
|
|
1149
|
+
}),
|
|
1150
|
+
},
|
|
1151
|
+
];
|
|
1152
|
+
|
|
1153
|
+
const result = await client.deploySuggestions(
|
|
1154
|
+
mockSite,
|
|
1155
|
+
mockOpportunity,
|
|
1156
|
+
mockSuggestions,
|
|
1157
|
+
);
|
|
1158
|
+
|
|
1159
|
+
// No suggestions deployed - returns early before metaconfig check
|
|
1160
|
+
expect(result.succeededSuggestions).to.have.length(0);
|
|
1161
|
+
expect(result.failedSuggestions).to.have.length(1);
|
|
1162
|
+
// fetchMetaconfig should not be called at all (returns before that point)
|
|
1163
|
+
expect(client.fetchMetaconfig).to.not.have.been.called;
|
|
1164
|
+
// uploadMetaconfig should not be called at all
|
|
1165
|
+
expect(client.uploadMetaconfig).to.not.have.been.called;
|
|
1166
|
+
});
|
|
1167
|
+
|
|
1168
|
+
it('should not update metaconfig when all URLs fail to generate configs', async () => {
|
|
1169
|
+
// Stub generateConfig to return null (no config generated)
|
|
1170
|
+
sinon.stub(client, 'generateConfig').returns(null);
|
|
1171
|
+
|
|
1172
|
+
const result = await client.deploySuggestions(
|
|
1173
|
+
mockSite,
|
|
1174
|
+
mockOpportunity,
|
|
1175
|
+
mockSuggestions,
|
|
1176
|
+
);
|
|
1177
|
+
|
|
1178
|
+
// Suggestions are marked as succeeded (eligible) but no configs uploaded
|
|
1179
|
+
expect(result.succeededSuggestions).to.have.length(2);
|
|
1180
|
+
expect(result.s3Paths).to.have.length(0); // No configs uploaded
|
|
1181
|
+
// fetchMetaconfig called once for initial check, but not for update
|
|
1182
|
+
// since no URLs were actually deployed (deployedUrls is empty)
|
|
1183
|
+
expect(client.fetchMetaconfig).to.have.been.calledOnce;
|
|
1184
|
+
// uploadMetaconfig should not be called at all
|
|
1185
|
+
expect(client.uploadMetaconfig).to.not.have.been.called;
|
|
1186
|
+
});
|
|
1187
|
+
|
|
1033
1188
|
it('should skip URL when generateConfig returns no patches', async () => {
|
|
1034
1189
|
// Stub mapper to return empty patches for the first call, normal for subsequent calls
|
|
1035
1190
|
const mapper = client.mapperRegistry.getMapper('headings');
|