@adobe/spacecat-shared-tokowaka-client 1.9.0 → 1.10.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 +6 -0
- package/package.json +1 -1
- package/src/index.js +66 -13
- package/test/index.test.js +130 -1
package/CHANGELOG.md
CHANGED
|
@@ -1,3 +1,9 @@
|
|
|
1
|
+
## [@adobe/spacecat-shared-tokowaka-client-v1.10.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.9.0...@adobe/spacecat-shared-tokowaka-client-v1.10.0) (2026-02-26)
|
|
2
|
+
|
|
3
|
+
### Features
|
|
4
|
+
|
|
5
|
+
* LLMO-2805 Stage config addition api changes ([#1370](https://github.com/adobe/spacecat-shared/issues/1370)) ([0789f4a](https://github.com/adobe/spacecat-shared/commit/0789f4af95433b733c86638a79c184850804dba9))
|
|
6
|
+
|
|
1
7
|
## [@adobe/spacecat-shared-tokowaka-client-v1.9.0](https://github.com/adobe/spacecat-shared/compare/@adobe/spacecat-shared-tokowaka-client-v1.8.0...@adobe/spacecat-shared-tokowaka-client-v1.9.0) (2026-02-24)
|
|
2
8
|
|
|
3
9
|
### Features
|
package/package.json
CHANGED
package/src/index.js
CHANGED
|
@@ -230,11 +230,13 @@ class TokowakaClient {
|
|
|
230
230
|
}
|
|
231
231
|
|
|
232
232
|
/**
|
|
233
|
-
*
|
|
233
|
+
* Internal method to fetch domain-level metaconfig from S3 with metadata
|
|
234
234
|
* @param {string} url - Full URL (used to extract domain)
|
|
235
|
-
* @returns {Promise<Object|null>} -
|
|
235
|
+
* @returns {Promise<Object|null>} - Object with metaconfig and s3Metadata,
|
|
236
|
+
* or null if not found
|
|
237
|
+
* @private
|
|
236
238
|
*/
|
|
237
|
-
async
|
|
239
|
+
async #fetchMetaconfigWithMetadata(url) {
|
|
238
240
|
if (!hasText(url)) {
|
|
239
241
|
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
240
242
|
}
|
|
@@ -254,7 +256,11 @@ class TokowakaClient {
|
|
|
254
256
|
const metaconfig = JSON.parse(bodyContents);
|
|
255
257
|
|
|
256
258
|
this.log.debug(`Successfully fetched metaconfig from s3://${bucketName}/${s3Path} in ${Date.now() - fetchStartTime}ms`);
|
|
257
|
-
|
|
259
|
+
|
|
260
|
+
return {
|
|
261
|
+
metaconfig,
|
|
262
|
+
s3Metadata: response.Metadata || {},
|
|
263
|
+
};
|
|
258
264
|
} catch (error) {
|
|
259
265
|
// If metaconfig doesn't exist (NoSuchKey), return null
|
|
260
266
|
if (error.name === 'NoSuchKey' || error.Code === 'NoSuchKey') {
|
|
@@ -268,6 +274,16 @@ class TokowakaClient {
|
|
|
268
274
|
}
|
|
269
275
|
}
|
|
270
276
|
|
|
277
|
+
/**
|
|
278
|
+
* Fetches domain-level metaconfig from S3
|
|
279
|
+
* @param {string} url - Full URL (used to extract domain)
|
|
280
|
+
* @returns {Promise<Object|null>} - Metaconfig object or null if not found
|
|
281
|
+
*/
|
|
282
|
+
async fetchMetaconfig(url) {
|
|
283
|
+
const result = await this.#fetchMetaconfigWithMetadata(url);
|
|
284
|
+
return result?.metaconfig ?? null;
|
|
285
|
+
}
|
|
286
|
+
|
|
271
287
|
/**
|
|
272
288
|
* Generates an API key for Tokowaka based on domain
|
|
273
289
|
* @param {string} domain - Domain name (e.g., 'example.com')
|
|
@@ -289,7 +305,13 @@ class TokowakaClient {
|
|
|
289
305
|
* @param {string} url - Full URL (used to extract domain)
|
|
290
306
|
* @param {string} siteId - Site ID
|
|
291
307
|
* @param {Object} options - Optional configuration
|
|
292
|
-
* @param {boolean} options.enhancements - Whether to enable enhancements
|
|
308
|
+
* @param {boolean} options.enhancements - Whether to enable enhancements
|
|
309
|
+
* (default: true)
|
|
310
|
+
* @param {Object} metadata - Optional S3 user-defined metadata for
|
|
311
|
+
* audit trail and behavior flags
|
|
312
|
+
* @param {string} metadata.lastModifiedBy - User who modified the config
|
|
313
|
+
* @param {boolean} metadata.isStageDomain - Whether this is a staging
|
|
314
|
+
* domain (enables wildcard prerender)
|
|
293
315
|
* @returns {Promise<Object>} - Object with s3Path and metaconfig
|
|
294
316
|
*/
|
|
295
317
|
async createMetaconfig(url, siteId, options = {}, metadata = {}) {
|
|
@@ -318,7 +340,19 @@ class TokowakaClient {
|
|
|
318
340
|
patches: {},
|
|
319
341
|
};
|
|
320
342
|
|
|
321
|
-
|
|
343
|
+
// Handle staging domain with automatic prerender configuration
|
|
344
|
+
const isStageDomain = metadata.isStageDomain === true;
|
|
345
|
+
if (isStageDomain) {
|
|
346
|
+
metaconfig.prerender = { allowList: ['/*'] };
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
// Persist isStageDomain in S3 metadata for future updates
|
|
350
|
+
const s3Metadata = {
|
|
351
|
+
...metadata,
|
|
352
|
+
...(isStageDomain && { isStageDomain: 'true' }),
|
|
353
|
+
};
|
|
354
|
+
|
|
355
|
+
const s3Path = await this.uploadMetaconfig(url, metaconfig, s3Metadata);
|
|
322
356
|
this.log.info(`Created new Tokowaka metaconfig for ${normalizedHostName} at ${s3Path}`);
|
|
323
357
|
|
|
324
358
|
return metaconfig;
|
|
@@ -330,6 +364,11 @@ class TokowakaClient {
|
|
|
330
364
|
* @param {string} url - Full URL (used to extract domain)
|
|
331
365
|
* @param {string} siteId - Site ID
|
|
332
366
|
* @param {Object} options - Optional configuration
|
|
367
|
+
* @param {Object} metadata - Optional S3 user-defined metadata for
|
|
368
|
+
* audit trail and behavior flags
|
|
369
|
+
* @param {string} metadata.lastModifiedBy - User who modified the config
|
|
370
|
+
* @param {boolean} metadata.isStageDomain - Whether this is a staging
|
|
371
|
+
* domain (enables wildcard prerender)
|
|
333
372
|
* @returns {Promise<Object>} - Object with s3Path and metaconfig
|
|
334
373
|
*/
|
|
335
374
|
async updateMetaconfig(url, siteId, options = {}, metadata = {}) {
|
|
@@ -337,10 +376,11 @@ class TokowakaClient {
|
|
|
337
376
|
throw this.#createError('URL is required', HTTP_BAD_REQUEST);
|
|
338
377
|
}
|
|
339
378
|
|
|
340
|
-
const
|
|
341
|
-
if (!
|
|
379
|
+
const raw = await this.#fetchMetaconfigWithMetadata(url);
|
|
380
|
+
if (!raw?.metaconfig) {
|
|
342
381
|
throw this.#createError('Metaconfig does not exist for this URL', HTTP_BAD_REQUEST);
|
|
343
382
|
}
|
|
383
|
+
const { metaconfig: existingMetaconfig, s3Metadata: existingS3Metadata } = raw;
|
|
344
384
|
|
|
345
385
|
if (!hasText(siteId)) {
|
|
346
386
|
throw this.#createError('Site ID is required', HTTP_BAD_REQUEST);
|
|
@@ -356,10 +396,17 @@ class TokowakaClient {
|
|
|
356
396
|
?? existingMetaconfig.forceFail
|
|
357
397
|
?? false;
|
|
358
398
|
|
|
359
|
-
|
|
399
|
+
// Handle staging domain: check from metadata or from existing S3 metadata (S3 lowercases keys)
|
|
400
|
+
const isStageDomain = metadata.isStageDomain === true
|
|
401
|
+
|| existingS3Metadata.isstagedomain === 'true';
|
|
402
|
+
|
|
403
|
+
const hasPrerender = isStageDomain
|
|
404
|
+
|| isNonEmptyObject(options.prerender)
|
|
360
405
|
|| isNonEmptyObject(existingMetaconfig.prerender);
|
|
361
|
-
|
|
362
|
-
|
|
406
|
+
|
|
407
|
+
const prerender = isStageDomain
|
|
408
|
+
? { allowList: ['/*'] }
|
|
409
|
+
: (options.prerender ?? existingMetaconfig.prerender);
|
|
363
410
|
|
|
364
411
|
const metaconfig = {
|
|
365
412
|
...existingMetaconfig,
|
|
@@ -372,8 +419,14 @@ class TokowakaClient {
|
|
|
372
419
|
...(hasPrerender && { prerender }),
|
|
373
420
|
};
|
|
374
421
|
|
|
375
|
-
|
|
376
|
-
|
|
422
|
+
// Persist isStageDomain in S3 metadata for future updates
|
|
423
|
+
const s3Metadata = {
|
|
424
|
+
...metadata,
|
|
425
|
+
...(isStageDomain && { isStageDomain: 'true' }),
|
|
426
|
+
};
|
|
427
|
+
|
|
428
|
+
const uploadedPath = await this.uploadMetaconfig(url, metaconfig, s3Metadata);
|
|
429
|
+
this.log.info(`Updated Tokowaka metaconfig for ${normalizedHostName} at ${uploadedPath}`);
|
|
377
430
|
|
|
378
431
|
return metaconfig;
|
|
379
432
|
}
|
package/test/index.test.js
CHANGED
|
@@ -649,6 +649,56 @@ describe('TokowakaClient', () => {
|
|
|
649
649
|
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
650
650
|
expect(uploadCommand.input.Metadata).to.be.undefined;
|
|
651
651
|
});
|
|
652
|
+
|
|
653
|
+
it('should NOT include prerender when isStageDomain is not true in metadata', async () => {
|
|
654
|
+
const siteId = 'site-123';
|
|
655
|
+
const url = 'https://www.example.com/page1';
|
|
656
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
657
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
658
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
659
|
+
|
|
660
|
+
const result = await client.createMetaconfig(url, siteId);
|
|
661
|
+
|
|
662
|
+
expect(result).to.not.have.property('prerender');
|
|
663
|
+
|
|
664
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
665
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
666
|
+
expect(body).to.not.have.property('prerender');
|
|
667
|
+
});
|
|
668
|
+
|
|
669
|
+
it('should set prerender with allowList when isStageDomain is true in metadata', async () => {
|
|
670
|
+
const siteId = 'site-123';
|
|
671
|
+
const url = 'https://staging.example.com/page1';
|
|
672
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
673
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
674
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
675
|
+
|
|
676
|
+
const result = await client.createMetaconfig(url, siteId, {}, { isStageDomain: true });
|
|
677
|
+
|
|
678
|
+
expect(result).to.have.property('prerender');
|
|
679
|
+
expect(result.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
680
|
+
|
|
681
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
682
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
683
|
+
expect(body.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
684
|
+
expect(uploadCommand.input.Metadata).to.deep.equal({ isStageDomain: 'true' });
|
|
685
|
+
});
|
|
686
|
+
|
|
687
|
+
it('should NOT set prerender when isStageDomain is false in metadata', async () => {
|
|
688
|
+
const siteId = 'site-123';
|
|
689
|
+
const url = 'https://www.example.com/page1';
|
|
690
|
+
const noSuchKeyError = new Error('NoSuchKey');
|
|
691
|
+
noSuchKeyError.name = 'NoSuchKey';
|
|
692
|
+
s3Client.send.onFirstCall().rejects(noSuchKeyError);
|
|
693
|
+
|
|
694
|
+
const result = await client.createMetaconfig(url, siteId, {}, { isStageDomain: false });
|
|
695
|
+
|
|
696
|
+
expect(result).to.not.have.property('prerender');
|
|
697
|
+
|
|
698
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
699
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
700
|
+
expect(body).to.not.have.property('prerender');
|
|
701
|
+
});
|
|
652
702
|
});
|
|
653
703
|
|
|
654
704
|
describe('updateMetaconfig', () => {
|
|
@@ -661,11 +711,12 @@ describe('TokowakaClient', () => {
|
|
|
661
711
|
};
|
|
662
712
|
|
|
663
713
|
beforeEach(() => {
|
|
664
|
-
// Mock fetchMetaconfig to return existing config
|
|
714
|
+
// Mock fetchMetaconfig to return existing config with metadata
|
|
665
715
|
s3Client.send.onFirstCall().resolves({
|
|
666
716
|
Body: {
|
|
667
717
|
transformToString: sinon.stub().resolves(JSON.stringify(existingMetaconfig)),
|
|
668
718
|
},
|
|
719
|
+
Metadata: {},
|
|
669
720
|
});
|
|
670
721
|
// Mock uploadMetaconfig S3 upload
|
|
671
722
|
s3Client.send.onSecondCall().resolves();
|
|
@@ -1431,6 +1482,84 @@ describe('TokowakaClient', () => {
|
|
|
1431
1482
|
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
1432
1483
|
expect(uploadCommand.input.Metadata).to.be.undefined;
|
|
1433
1484
|
});
|
|
1485
|
+
|
|
1486
|
+
it('should set prerender with allowList when isStageDomain is true in metadata', async () => {
|
|
1487
|
+
const siteId = 'site-456';
|
|
1488
|
+
const url = 'https://staging.example.com';
|
|
1489
|
+
|
|
1490
|
+
const result = await client.updateMetaconfig(url, siteId, {}, { isStageDomain: true });
|
|
1491
|
+
|
|
1492
|
+
expect(result).to.have.property('prerender');
|
|
1493
|
+
expect(result.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
1494
|
+
|
|
1495
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
1496
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
1497
|
+
expect(body.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
1498
|
+
expect(uploadCommand.input.Metadata).to.deep.equal({ isStageDomain: 'true' });
|
|
1499
|
+
});
|
|
1500
|
+
|
|
1501
|
+
it('should override existing prerender when isStageDomain is true in metadata', async () => {
|
|
1502
|
+
const siteId = 'site-456';
|
|
1503
|
+
const url = 'https://staging.example.com';
|
|
1504
|
+
const existingMetaconfigWithPrerender = {
|
|
1505
|
+
...existingMetaconfig,
|
|
1506
|
+
prerender: { allowList: ['/old-path/*'] },
|
|
1507
|
+
};
|
|
1508
|
+
|
|
1509
|
+
s3Client.send.onFirstCall().resolves({
|
|
1510
|
+
Body: {
|
|
1511
|
+
transformToString: sinon.stub().resolves(JSON.stringify(existingMetaconfigWithPrerender)),
|
|
1512
|
+
},
|
|
1513
|
+
Metadata: {},
|
|
1514
|
+
});
|
|
1515
|
+
|
|
1516
|
+
const result = await client.updateMetaconfig(url, siteId, {}, { isStageDomain: true });
|
|
1517
|
+
|
|
1518
|
+
expect(result).to.have.property('prerender');
|
|
1519
|
+
expect(result.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
1520
|
+
|
|
1521
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
1522
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
1523
|
+
expect(body.prerender).to.deep.equal({ allowList: ['/*'] });
|
|
1524
|
+
});
|
|
1525
|
+
|
|
1526
|
+
it('should preserve existing prerender when isStageDomain is not in metadata', async () => {
|
|
1527
|
+
const siteId = 'site-456';
|
|
1528
|
+
const url = 'https://www.example.com';
|
|
1529
|
+
const existingMetaconfigWithPrerender = {
|
|
1530
|
+
...existingMetaconfig,
|
|
1531
|
+
prerender: { allowList: ['/path/*'] },
|
|
1532
|
+
};
|
|
1533
|
+
|
|
1534
|
+
s3Client.send.onFirstCall().resolves({
|
|
1535
|
+
Body: {
|
|
1536
|
+
transformToString: sinon.stub().resolves(JSON.stringify(existingMetaconfigWithPrerender)),
|
|
1537
|
+
},
|
|
1538
|
+
Metadata: {},
|
|
1539
|
+
});
|
|
1540
|
+
|
|
1541
|
+
const result = await client.updateMetaconfig(url, siteId, {});
|
|
1542
|
+
|
|
1543
|
+
expect(result).to.have.property('prerender');
|
|
1544
|
+
expect(result.prerender).to.deep.equal({ allowList: ['/path/*'] });
|
|
1545
|
+
|
|
1546
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
1547
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
1548
|
+
expect(body.prerender).to.deep.equal({ allowList: ['/path/*'] });
|
|
1549
|
+
});
|
|
1550
|
+
|
|
1551
|
+
it('should NOT set prerender when isStageDomain is false in metadata', async () => {
|
|
1552
|
+
const siteId = 'site-456';
|
|
1553
|
+
const url = 'https://www.example.com';
|
|
1554
|
+
|
|
1555
|
+
const result = await client.updateMetaconfig(url, siteId, {}, { isStageDomain: false });
|
|
1556
|
+
|
|
1557
|
+
expect(result).to.not.have.property('prerender');
|
|
1558
|
+
|
|
1559
|
+
const uploadCommand = s3Client.send.secondCall.args[0];
|
|
1560
|
+
const body = JSON.parse(uploadCommand.input.Body);
|
|
1561
|
+
expect(body).to.not.have.property('prerender');
|
|
1562
|
+
});
|
|
1434
1563
|
});
|
|
1435
1564
|
|
|
1436
1565
|
describe('uploadConfig', () => {
|