From 3472b2fa29a8edee71d77b534f35c55aefc8a095 Mon Sep 17 00:00:00 2001 From: Nadeem Patwekar Date: Wed, 28 May 2025 13:17:57 +0530 Subject: [PATCH 1/5] enh: modify retry logic based on ratelimit remaining header --- CHANGELOG.md | 4 ++ lib/core/concurrency-queue.js | 30 ++++++++----- package-lock.json | 64 +------------------------- package.json | 2 +- test/unit/concurrency-Queue-test.js | 70 +++++++++++++++++++++++++++++ 5 files changed, 96 insertions(+), 74 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b638938a..6b74d43c 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## [v1.21.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.4) (2025-06-02) + - Enhancement + - Retry Logic modification on x-ratelimit-remaining Header + ## [v1.21.3](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.3) (2025-05-26) - Enhancement - Update addSettings Method to Support Generic Stack Settings Update diff --git a/lib/core/concurrency-queue.js b/lib/core/concurrency-queue.js index 868307e3..247184bd 100644 --- a/lib/core/concurrency-queue.js +++ b/lib/core/concurrency-queue.js @@ -229,6 +229,7 @@ export function ConcurrencyQueue ({ axios, config }) { if (!this.config.retryOnError || networkError > this.config.retryLimit) { return Promise.reject(responseHandler(error)) } + // Check rate limit remaining header before retrying // Error handling const wait = this.config.retryDelay @@ -244,19 +245,26 @@ export function ConcurrencyQueue ({ axios, config }) { } else { return Promise.reject(responseHandler(error)) } - } else if ((response.status === 401 && this.config.refreshToken)) { - retryErrorType = `Error with status: ${response.status}` - networkError++ - - if (networkError > this.config.retryLimit) { + } else { + const rateLimitRemaining = response.headers['x-ratelimit-remaining'] + if (rateLimitRemaining !== undefined && parseInt(rateLimitRemaining) <= 0) { return Promise.reject(responseHandler(error)) } - this.running.shift() - // Cool down the running requests - delay(wait, response.status === 401) - error.config.retryCount = networkError - // deepcode ignore Ssrf: URL is dynamic - return axios(updateRequestConfig(error, retryErrorType, wait)) + + if ((response.status === 401 && this.config.refreshToken)) { + retryErrorType = `Error with status: ${response.status}` + networkError++ + + if (networkError > this.config.retryLimit) { + return Promise.reject(responseHandler(error)) + } + this.running.shift() + // Cool down the running requests + delay(wait, response.status === 401) + error.config.retryCount = networkError + // deepcode ignore Ssrf: URL is dynamic + return axios(updateRequestConfig(error, retryErrorType, wait)) + } } if (this.config.retryCondition && this.config.retryCondition(error)) { retryErrorType = error.response ? `Error with status: ${response.status}` : `Error Code:${error.code}` diff --git a/package-lock.json b/package-lock.json index 32aa93f5..be7d6f59 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/management", - "version": "1.21.3", + "version": "1.21.4", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/management", - "version": "1.21.3", + "version": "1.21.4", "license": "MIT", "dependencies": { "assert": "^2.1.0", @@ -83,8 +83,6 @@ }, "node_modules/@babel/cli": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/cli/-/cli-7.27.0.tgz", - "integrity": "sha512-bZfxn8DRxwiVzDO5CEeV+7IqXeCkzI4yYnrQbpwjT76CUyossQc6RYE7n+xfm0/2k40lPaCpW0FhxYs7EBAetw==", "dev": true, "license": "MIT", "dependencies": { @@ -134,8 +132,6 @@ }, "node_modules/@babel/core": { "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.26.10.tgz", - "integrity": "sha512-vMqyb7XCDMPvJFFOaT9kxtiRh42GwlZEg1/uIgtZshS5a/8OaduUfCi7kynKgc3Tw/6Uo2D+db9qBttghhmxwQ==", "dev": true, "license": "MIT", "dependencies": { @@ -165,8 +161,6 @@ }, "node_modules/@babel/eslint-parser": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.27.0.tgz", - "integrity": "sha512-dtnzmSjXfgL/HDgMcmsLSzyGbEosi4DrGWoCNfuI+W4IkVJw6izpTe7LtOdwAXnkDqw5yweboYCTkM2rQizCng==", "dev": true, "license": "MIT", "dependencies": { @@ -184,8 +178,6 @@ }, "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", - "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", "dev": true, "license": "Apache-2.0", "engines": { @@ -194,8 +186,6 @@ }, "node_modules/@babel/generator": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.27.0.tgz", - "integrity": "sha512-VybsKvpiN1gU1sdMZIp7FcqphVVKEwcuj02x73uvcHE0PTihx1nlBcowYWhDwjpoAXRv43+gDzyggGnn1XZhVw==", "dev": true, "license": "MIT", "dependencies": { @@ -428,8 +418,6 @@ }, "node_modules/@babel/helpers": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.27.0.tgz", - "integrity": "sha512-U5eyP/CTFPuNE3qk+WZMxFkp/4zUzdceQlfzf7DdGdhp+Fezd7HD+i8Y24ZuTMKX3wQBld449jijbGq6OdGNQg==", "dev": true, "license": "MIT", "dependencies": { @@ -442,8 +430,6 @@ }, "node_modules/@babel/parser": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.27.0.tgz", - "integrity": "sha512-iaepho73/2Pz7w2eMS0Q5f83+0RKI7i4xmiYeBmDzfRVbQtTOG7Ts0S4HzJVsTMGI9keU8rNfuZr8DKfSt7Yyg==", "dev": true, "license": "MIT", "dependencies": { @@ -1370,8 +1356,6 @@ }, "node_modules/@babel/plugin-transform-runtime": { "version": "7.26.10", - "resolved": "https://registry.npmjs.org/@babel/plugin-transform-runtime/-/plugin-transform-runtime-7.26.10.tgz", - "integrity": "sha512-NWaL2qG6HRpONTnj4JvDU6th4jYeZOJgu3QhmFTCihib0ermtOJqktA5BduGm3suhhVe9EMP9c9+mfJ/I9slqw==", "dev": true, "license": "MIT", "dependencies": { @@ -1634,8 +1618,6 @@ }, "node_modules/@babel/runtime": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.27.0.tgz", - "integrity": "sha512-VtPOkrdPHZsKc/clNqyi9WUA8TINkZ4cGk63UUE3u4pmB2k+ZMQRDuIOagv8UVd6j7k0T3+RRIb7beKTebNbcw==", "dev": true, "license": "MIT", "dependencies": { @@ -1647,8 +1629,6 @@ }, "node_modules/@babel/template": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.27.0.tgz", - "integrity": "sha512-2ncevenBqXI6qRMukPlXwHKHchC7RyMuu4xv5JBXRfOGVcTy1mXCD12qrp7Jsoxll1EV3+9sE4GugBVRjT2jFA==", "dev": true, "license": "MIT", "dependencies": { @@ -1662,8 +1642,6 @@ }, "node_modules/@babel/traverse": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.27.0.tgz", - "integrity": "sha512-19lYZFzYVQkkHkl4Cy4WrAVcqBkgvV2YM2TU3xG6DIwO7O3ecbDPfW3yM3bjAGcqcQHi+CCtjMR3dIEHxsd6bA==", "dev": true, "license": "MIT", "dependencies": { @@ -1681,8 +1659,6 @@ }, "node_modules/@babel/types": { "version": "7.27.0", - "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.27.0.tgz", - "integrity": "sha512-H45s8fVLYjbhFH62dIJ3WtmJ6RSPt/3DRO0ZcT2SUiYiQyz3BLVb9ADEnLl91m74aQPS3AzzeajZHYOalWe3bg==", "dev": true, "license": "MIT", "dependencies": { @@ -2637,8 +2613,6 @@ }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": { "version": "5.1.1-v1", - "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz", - "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==", "dev": true, "license": "MIT", "dependencies": { @@ -2647,8 +2621,6 @@ }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": { "version": "5.1.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", - "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", "dev": true, "license": "BSD-2-Clause", "dependencies": { @@ -2661,8 +2633,6 @@ }, "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": { "version": "4.3.0", - "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", - "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -3087,8 +3057,6 @@ }, "node_modules/@types/mocha": { "version": "8.2.3", - "resolved": "https://registry.npmjs.org/@types/mocha/-/mocha-8.2.3.tgz", - "integrity": "sha512-ekGvFhFgrc2zYQoX4JeZPmVzZxw6Dtllga7iGHzfbYIYkAMUx/sAFP2GdFpLff+vdHXu5fl7WX9AT+TtqYcsyw==", "dev": true, "license": "MIT" }, @@ -3667,8 +3635,6 @@ }, "node_modules/assert": { "version": "2.1.0", - "resolved": "https://registry.npmjs.org/assert/-/assert-2.1.0.tgz", - "integrity": "sha512-eLHpSK/Y4nhMJ07gDaAzoX/XAKS8PSaojml3M0DM4JpV1LAi5JOJ/p6H/XWrl8L+DzVEvVCW1z3vWAaB9oTsQw==", "license": "MIT", "dependencies": { "call-bind": "^1.0.2", @@ -3713,8 +3679,6 @@ }, "node_modules/axios": { "version": "1.9.0", - "resolved": "https://registry.npmjs.org/axios/-/axios-1.9.0.tgz", - "integrity": "sha512-re4CqKTJaURpzbLHtIi6XpDv20/CnpXOtjRY5/CU32L8gU8ek9UIivcfvSWvmKEngmVbrUtPpdDwWDWL7DNHvg==", "license": "MIT", "dependencies": { "follow-redirects": "^1.15.6", @@ -3981,8 +3945,6 @@ }, "node_modules/babel-plugin-polyfill-corejs3": { "version": "0.11.1", - "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.11.1.tgz", - "integrity": "sha512-yGCqvBT4rwMczo28xkH/noxJ6MZ4nJfkVYdoDaC/utLtWrXxv27HVrzAeSbqR8SxDsp46n0YF47EbHoixy6rXQ==", "dev": true, "license": "MIT", "dependencies": { @@ -4173,8 +4135,6 @@ }, "node_modules/base64-js": { "version": "1.5.1", - "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", - "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", "funding": [ { "type": "github", @@ -4311,8 +4271,6 @@ }, "node_modules/buffer": { "version": "6.0.3", - "resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz", - "integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==", "funding": [ { "type": "github", @@ -5045,8 +5003,6 @@ }, "node_modules/dotenv": { "version": "16.5.0", - "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.5.0.tgz", - "integrity": "sha512-m/C+AwOAr9/W1UOIZUo232ejMNnJAJtYQjUbHoNTBNTJSvqzzDh7vnrei3o3r3m9blf6ZoDkvcw0VmozNRFJxg==", "dev": true, "license": "BSD-2-Clause", "engines": { @@ -6577,8 +6533,6 @@ }, "node_modules/husky": { "version": "9.1.7", - "resolved": "https://registry.npmjs.org/husky/-/husky-9.1.7.tgz", - "integrity": "sha512-5gs5ytaNjBrh5Ow3zrvdUUY+0VxIuWVL4i9irt6friV+BqdCfmV11CQTWMiBYWHbXhco+J1kHfTOUkePhCDvMA==", "license": "MIT", "bin": { "husky": "bin.js" @@ -6603,8 +6557,6 @@ }, "node_modules/ieee754": { "version": "1.2.1", - "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", - "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", "funding": [ { "type": "github", @@ -7040,8 +6992,6 @@ }, "node_modules/is-nan": { "version": "1.3.2", - "resolved": "https://registry.npmjs.org/is-nan/-/is-nan-1.3.2.tgz", - "integrity": "sha512-E+zBKpQ2t6MEo1VsonYmluk9NxGrbzpeeLC2xIViuO2EjU2xsXsBPwTr3Ykv9l08UYEVEdWeRZNouaZqF6RN0w==", "license": "MIT", "dependencies": { "call-bind": "^1.0.0", @@ -10724,8 +10674,6 @@ }, "node_modules/readable-stream": { "version": "3.6.2", - "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", - "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -11616,8 +11564,6 @@ }, "node_modules/stream-browserify": { "version": "3.0.0", - "resolved": "https://registry.npmjs.org/stream-browserify/-/stream-browserify-3.0.0.tgz", - "integrity": "sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==", "license": "MIT", "dependencies": { "inherits": "~2.0.4", @@ -11626,8 +11572,6 @@ }, "node_modules/string_decoder": { "version": "1.3.0", - "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", - "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", "license": "MIT", "dependencies": { "safe-buffer": "~5.2.0" @@ -12486,8 +12430,6 @@ }, "node_modules/util": { "version": "0.12.5", - "resolved": "https://registry.npmjs.org/util/-/util-0.12.5.tgz", - "integrity": "sha512-kZf/K6hEIrWHI6XqOFUiiMa+79wE/D8Q+NCNAWclkyg3b4d2k7s0QGepNjiABc+aR3N1PAyHL7p6UcLY6LmrnA==", "license": "MIT", "dependencies": { "inherits": "^2.0.3", @@ -12499,8 +12441,6 @@ }, "node_modules/util-deprecate": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", "license": "MIT" }, "node_modules/utils-merge": { diff --git a/package.json b/package.json index d2e6a11a..e2844460 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/management", - "version": "1.21.3", + "version": "1.21.4", "description": "The Content Management API is used to manage the content of your Contentstack account", "main": "./dist/node/contentstack-management.js", "browser": "./dist/web/contentstack-management.js", diff --git a/test/unit/concurrency-Queue-test.js b/test/unit/concurrency-Queue-test.js index de3e2197..f90e85c2 100644 --- a/test/unit/concurrency-Queue-test.js +++ b/test/unit/concurrency-Queue-test.js @@ -506,6 +506,76 @@ describe('Concurrency queue test', () => { }) .catch(done) }) + + it('should not retry when rate limit remaining is 0', done => { + const mock = new MockAdapter(api) + mock.onGet('/ratelimit').reply(429, { errorCode: 429 }, { + 'x-ratelimit-remaining': '0' + }) + + reconfigureQueue({ + retryOnError: true, + retryLimit: 3, + retryDelay: 300 + }) + + api.get('/ratelimit') + .catch(error => { + expect(error.response.status).to.equal(429) + expect(error.config.retryCount).to.equal(0) // Should not have retried + expect(logHandlerStub.callCount).to.equal(0) // No retry logs + mock.restore() + done() + }) + }) + + it('should retry when rate limit remaining is greater than 0', done => { + const mock = new MockAdapter(api) + mock.onGet('/ratelimit').reply(429, { errorCode: 429 }, { + 'x-ratelimit-remaining': '5' + }) + mock.onGet('/ratelimit').reply(200, { success: true }) + + reconfigureQueue({ + retryOnError: true, + retryLimit: 3, + retryDelay: 300 + }) + + api.get('/ratelimit') + .then(response => { + /* eslint-disable no-unused-expressions */ + expect(response.status).to.equal(200) + expect(response.data.success).to.be.true + /* eslint-enable no-unused-expressions */ + mock.restore() + done() + }) + .catch(done) + }) + + it('should retry when rate limit remaining header is not present', done => { + const mock = new MockAdapter(api) + mock.onGet('/ratelimit').reply(429, { errorCode: 429 }) + mock.onGet('/ratelimit').reply(200, { success: true }) + + reconfigureQueue({ + retryOnError: true, + retryLimit: 3, + retryDelay: 300 + }) + + api.get('/ratelimit') + .then(response => { + /* eslint-disable no-unused-expressions */ + expect(response.status).to.equal(200) + expect(response.data.success).to.be.true + /* eslint-enable no-unused-expressions */ + mock.restore() + done() + }) + .catch(done) + }) }) function makeConcurrencyQueue (config) { From 06cb2dc7797522f7526589c8a01d9eb6bd025f1e Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:04:31 +0530 Subject: [PATCH 2/5] Added support for preview token and optimize the create method --- CHANGELOG.md | 4 + lib/entity.js | 11 +-- lib/stack/deliveryToken/index.js | 20 +++- lib/stack/deliveryToken/previewToken/index.js | 50 ++++++++++ package-lock.json | 4 +- package.json | 2 +- test/sanity-check/api/previewToken-test.js | 91 +++++++++++++++++++ test/sanity-check/mock/deliveryToken.js | 28 +++++- types/stack/deliveryToken/previewToken.ts | 78 ++++++++++++++++ types/stack/index.d.ts | 3 - 10 files changed, 277 insertions(+), 14 deletions(-) create mode 100644 lib/stack/deliveryToken/previewToken/index.js create mode 100644 test/sanity-check/api/previewToken-test.js create mode 100644 types/stack/deliveryToken/previewToken.ts diff --git a/CHANGELOG.md b/CHANGELOG.md index 6b74d43c..13e1c417 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,4 +1,8 @@ # Changelog +## [v1.21.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.5) (2025-06-09) + - Enhancement + - Preview token support added + ## [v1.21.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.21.4) (2025-06-02) - Enhancement - Retry Logic modification on x-ratelimit-remaining Header diff --git a/lib/entity.js b/lib/entity.js index 266c10e2..ba98476e 100644 --- a/lib/entity.js +++ b/lib/entity.js @@ -85,21 +85,20 @@ export const upload = async ({ http, urlPath, stackHeaders, formData, params, me } } -export const create = ({ http, params = {}, createWithPreviewToken = false }) => { +export const create = ({ http, params }) => { return async function (data, param) { this.stackHeaders = { ...this.stackHeaders } - const queryParams = { - ...(createWithPreviewToken ? { create_with_preview_token: true } : {}), - ...cloneDeep(param) // user param can override default - } + const headers = { headers: { ...cloneDeep(params), ...cloneDeep(this.stackHeaders) }, - params: queryParams + params: { + ...cloneDeep(param) + } } || {} try { diff --git a/lib/stack/deliveryToken/index.js b/lib/stack/deliveryToken/index.js index 763423c9..9fa46f36 100644 --- a/lib/stack/deliveryToken/index.js +++ b/lib/stack/deliveryToken/index.js @@ -1,5 +1,6 @@ import cloneDeep from 'lodash/cloneDeep' import { create, update, deleteEntity, fetch, query } from '../../entity' +import { PreviewToken } from './previewToken' /** * Delivery tokens provide read-only access to the associated environments. Read more about DeliveryToken. @@ -59,6 +60,23 @@ export function DeliveryToken (http, data = {}) { * */ this.fetch = fetch(http, 'token') + + /** + * @description The Create a PreviewToken call creates a new previewToken in a particular stack of your Contentstack account. + * @memberof DeliveryToken + * @func previewToken + * @returns {PreviewToken} Instance of PreviewToken. + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * const deliveryToken = client.stack({ api_key: 'api_key'}).deliveryToken('delivery_token_uid') + * const previewToken = deliveryToken.previewToken() + * console.log(previewToken) + */ + this.previewToken = () => { + return new PreviewToken(http, { stackHeaders: this.stackHeaders, token: { uid: this.uid } }) + } + } else { /** * @description The Create a DeliveryToken call creates a new deliveryToken in a particular stack of your Contentstack account. @@ -84,7 +102,7 @@ export function DeliveryToken (http, data = {}) { * client.stack().deliveryToken().create({ token }) * .then((deliveryToken) => console.log(deliveryToken)) */ - this.create = create({ http: http, createWithPreviewToken: true }) + this.create = create({ http: http }) /** * @description The ‘Get all deliveryToken’ request returns comprehensive information about all deliveryToken created in a stack. diff --git a/lib/stack/deliveryToken/previewToken/index.js b/lib/stack/deliveryToken/previewToken/index.js new file mode 100644 index 00000000..34b2096b --- /dev/null +++ b/lib/stack/deliveryToken/previewToken/index.js @@ -0,0 +1,50 @@ +import cloneDeep from 'lodash/cloneDeep' +import { create, deleteEntity } from '../../../entity' + +/** + * Preview tokens provide read-only access to the associated environments. Read more about PreviewToken. + * @namespace PreviewToken + */ +export function PreviewToken (http, data = {}) { + this.stackHeaders = data.stackHeaders + if (data.token) { + Object.assign(this, cloneDeep(data.token)) + this.urlPath = `/stacks/delivery_tokens/${this.uid}/preview_token` + + /** + * @description The Delete PreviewToken call is used to delete an existing PreviewToken permanently from your Stack. + * @memberof PreviewToken + * @func delete + * @returns {Object} Response Object. + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * + * client.stack({ api_key: 'api_key'}).deliveryToken('delivery_token_uid').previewToken().delete() + * .then((response) => console.log(response.notice)) + */ + this.delete = deleteEntity(http) + + /** + * @description The Create a PreviewToken call creates a new previewToken in a particular stack of your Contentstack account. + * @memberof PreviewToken + * @func create + * @returns {Promise} Promise for PreviewToken instance + * + * @example + * import * as contentstack from '@contentstack/management' + * const client = contentstack.client() + * client.stack().deliveryToken('delivery_token_uid').previewToken().create() + * .then((previewToken) => console.log(previewToken)) + */ + this.create = create({ http: http }) + } +} + +export function PreviewTokenCollection (http, data) { + const obj = cloneDeep(data.tokens) || [] + const previewTokenCollection = obj.map((userdata) => { + return new PreviewToken(http, { token: userdata, stackHeaders: data.stackHeaders }) + }) + return previewTokenCollection +} \ No newline at end of file diff --git a/package-lock.json b/package-lock.json index be7d6f59..15c34fe4 100644 --- a/package-lock.json +++ b/package-lock.json @@ -1,12 +1,12 @@ { "name": "@contentstack/management", - "version": "1.21.4", + "version": "1.21.5", "lockfileVersion": 3, "requires": true, "packages": { "": { "name": "@contentstack/management", - "version": "1.21.4", + "version": "1.21.5", "license": "MIT", "dependencies": { "assert": "^2.1.0", diff --git a/package.json b/package.json index e2844460..2d302a11 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "@contentstack/management", - "version": "1.21.4", + "version": "1.21.5", "description": "The Content Management API is used to manage the content of your Contentstack account", "main": "./dist/node/contentstack-management.js", "browser": "./dist/web/contentstack-management.js", diff --git a/test/sanity-check/api/previewToken-test.js b/test/sanity-check/api/previewToken-test.js new file mode 100644 index 00000000..ab9384cb --- /dev/null +++ b/test/sanity-check/api/previewToken-test.js @@ -0,0 +1,91 @@ +import { expect } from 'chai' +import { describe, it, setup } from 'mocha' +import { jsonReader } from '../utility/fileOperations/readwrite' +import { createDeliveryToken3 } from '../mock/deliveryToken.js' +import { contentstackClient } from '../utility/ContentstackClient.js' +import dotenv from 'dotenv' + +dotenv.config(); +let client = {}; + +let tokenUID = ""; +describe("Preview Token api Test", () => { + setup(() => { + const user = jsonReader("loggedinuser.json"); + client = contentstackClient(user.authtoken); + }); + + it("should add a Delivery Token for development", (done) => { + makeDeliveryToken() + .create(createDeliveryToken3) + .then((token) => { + tokenUID = token.uid + expect(token.name).to.be.equal(createDeliveryToken3.token.name); + expect(token.description).to.be.equal( + createDeliveryToken3.token.description + ); + expect(token.scope[0].environments[0].name).to.be.equal( + createDeliveryToken3.token.scope[0].environments[0] + ); + expect(token.scope[0].module).to.be.equal( + createDeliveryToken3.token.scope[0].module + ); + expect(token.uid).to.be.not.equal(null); + expect(token.preview_token).to.be.not.equal(null); + done(); + }) + .catch(done); + }); + + it("should add a Preview Token", (done) => { + makePreviewToken(tokenUID) + .create() + .then((token) => { + expect(token.name).to.be.equal(createDeliveryToken3.token.name); + expect(token.description).to.be.equal( + createDeliveryToken3.token.description + ); + expect(token.scope[0].environments[0].name).to.be.equal( + createDeliveryToken3.token.scope[0].environments[0] + ); + expect(token.scope[0].module).to.be.equal( + createDeliveryToken3.token.scope[0].module + ); + expect(token.uid).to.be.not.equal(null); + expect(token.preview_token).to.be.not.equal(null); + done(); + }) + .catch(done); + }); + + it("should delete a Preview Token from uid", (done) => { + makePreviewToken(tokenUID) + .delete() + .then((data) => { + expect(data.notice).to.be.equal("Preview token deleted successfully."); + done(); + }) + .catch(done); + }); + + it("should delete a Delivery Token from uid", (done) => { + makeDeliveryToken(tokenUID) + .delete() + .then((data) => { + expect(data.notice).to.be.equal("Delivery Token deleted successfully."); + done(); + }) + .catch(done); + }); +}); + +function makePreviewToken(uid = null) { + return client + .stack({ api_key: process.env.API_KEY }) + .deliveryToken(uid) + .previewToken(); +} + +function makeDeliveryToken(uid = null) { + return client.stack({ api_key: process.env.API_KEY }).deliveryToken(uid); +} diff --git a/test/sanity-check/mock/deliveryToken.js b/test/sanity-check/mock/deliveryToken.js index aaf386f6..1090d2c5 100644 --- a/test/sanity-check/mock/deliveryToken.js +++ b/test/sanity-check/mock/deliveryToken.js @@ -70,5 +70,31 @@ const createDeliveryToken2 = { ] } } +const createDeliveryToken3 = { + token: { + name: 'preview token test', + description: 'This is a demo token.', + scope: [ + { + module: 'environment', + environments: [ + 'development' + ], + acl: { + read: true + } + }, + { + module: 'branch', + branches: [ + 'main', + ], + acl: { + read: true + } + } + ] + } +} -export { createDeliveryToken, createDeliveryToken2 } +export { createDeliveryToken, createDeliveryToken2, createDeliveryToken3 } diff --git a/types/stack/deliveryToken/previewToken.ts b/types/stack/deliveryToken/previewToken.ts new file mode 100644 index 00000000..1f5e6efb --- /dev/null +++ b/types/stack/deliveryToken/previewToken.ts @@ -0,0 +1,78 @@ +import { AnyProperty, SystemFields } from "../../utility/fields"; +import { Creatable, SystemFunction } from "../../utility/operations"; + +// Main preview token interface +export interface PreviewToken + extends SystemFields, + Creatable, + SystemFunction { + name: string; + description: string; + scope: Scope[]; + uid: string; + created_by: string; + updated_by: string; + created_at: string; + updated_at: string; + token: string; + type: string; + preview_token: string; +} + +// API response shape for creating a preview token +export interface PreviewTokenResponse { + notice: string; + token: PreviewTokenData; +} + +// Data inside the response `token` +export interface PreviewTokenData extends AnyProperty { + name: string; + description: string; + scope: Scope[]; + uid: string; + created_by: string; + updated_by: string; + created_at: string; + updated_at: string; + token: string; + type: string; + preview_token: string; +} + +export interface Scope { + module: string; + environments?: Environment[]; + branches?: string[]; + locales?: string[]; + acl: ACL; + _metadata?: { + uid: string; + }; +} + +export interface Environment extends AnyProperty { + name: string; + uid: string; + urls?: UrlLocale[]; + _version?: number; + app_user_object_uid?: string; + created_by?: string; + updated_by?: string; + created_at?: string; + updated_at?: string; + ACL?: unknown[]; + tags?: string[]; +} + +export interface UrlLocale { + url: string; + locale: string; +} + +export interface ACL extends AnyProperty { + read?: boolean; + write?: boolean; + create?: boolean; + update?: boolean; +} diff --git a/types/stack/index.d.ts b/types/stack/index.d.ts index 055ab895..0105d881 100644 --- a/types/stack/index.d.ts +++ b/types/stack/index.d.ts @@ -66,9 +66,6 @@ export interface Stack extends SystemFields { globalField(options: object): GlobalFields; globalField(uidOrOptions?: string | object, option?: object): GlobalFields | GlobalField; - - - asset(): Assets asset(uid: string): Asset From fcd1c0128d23ff01226a7583eb38d7ce48a82d98 Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:07:49 +0530 Subject: [PATCH 3/5] Fixed lint errors --- lib/entity.js | 2 +- lib/stack/deliveryToken/index.js | 1 - lib/stack/deliveryToken/previewToken/index.js | 4 +- test/sanity-check/api/previewToken-test.js | 84 +++++++++---------- test/sanity-check/mock/deliveryToken.js | 2 +- 5 files changed, 46 insertions(+), 47 deletions(-) diff --git a/lib/entity.js b/lib/entity.js index ba98476e..8088f720 100644 --- a/lib/entity.js +++ b/lib/entity.js @@ -90,7 +90,7 @@ export const create = ({ http, params }) => { this.stackHeaders = { ...this.stackHeaders } - + const headers = { headers: { ...cloneDeep(params), diff --git a/lib/stack/deliveryToken/index.js b/lib/stack/deliveryToken/index.js index 9fa46f36..12f8ac10 100644 --- a/lib/stack/deliveryToken/index.js +++ b/lib/stack/deliveryToken/index.js @@ -76,7 +76,6 @@ export function DeliveryToken (http, data = {}) { this.previewToken = () => { return new PreviewToken(http, { stackHeaders: this.stackHeaders, token: { uid: this.uid } }) } - } else { /** * @description The Create a DeliveryToken call creates a new deliveryToken in a particular stack of your Contentstack account. diff --git a/lib/stack/deliveryToken/previewToken/index.js b/lib/stack/deliveryToken/previewToken/index.js index 34b2096b..f438a09e 100644 --- a/lib/stack/deliveryToken/previewToken/index.js +++ b/lib/stack/deliveryToken/previewToken/index.js @@ -38,7 +38,7 @@ export function PreviewToken (http, data = {}) { * .then((previewToken) => console.log(previewToken)) */ this.create = create({ http: http }) - } + } } export function PreviewTokenCollection (http, data) { @@ -47,4 +47,4 @@ export function PreviewTokenCollection (http, data) { return new PreviewToken(http, { token: userdata, stackHeaders: data.stackHeaders }) }) return previewTokenCollection -} \ No newline at end of file +} diff --git a/test/sanity-check/api/previewToken-test.js b/test/sanity-check/api/previewToken-test.js index ab9384cb..a6a31047 100644 --- a/test/sanity-check/api/previewToken-test.js +++ b/test/sanity-check/api/previewToken-test.js @@ -5,87 +5,87 @@ import { createDeliveryToken3 } from '../mock/deliveryToken.js' import { contentstackClient } from '../utility/ContentstackClient.js' import dotenv from 'dotenv' -dotenv.config(); -let client = {}; +dotenv.config() +let client = {} -let tokenUID = ""; -describe("Preview Token api Test", () => { +let tokenUID = '' +describe('Preview Token api Test', () => { setup(() => { - const user = jsonReader("loggedinuser.json"); - client = contentstackClient(user.authtoken); - }); + const user = jsonReader('loggedinuser.json') + client = contentstackClient(user.authtoken) + }) - it("should add a Delivery Token for development", (done) => { + it('should add a Delivery Token for development', (done) => { makeDeliveryToken() .create(createDeliveryToken3) .then((token) => { tokenUID = token.uid - expect(token.name).to.be.equal(createDeliveryToken3.token.name); + expect(token.name).to.be.equal(createDeliveryToken3.token.name) expect(token.description).to.be.equal( createDeliveryToken3.token.description - ); + ) expect(token.scope[0].environments[0].name).to.be.equal( createDeliveryToken3.token.scope[0].environments[0] - ); + ) expect(token.scope[0].module).to.be.equal( createDeliveryToken3.token.scope[0].module - ); - expect(token.uid).to.be.not.equal(null); - expect(token.preview_token).to.be.not.equal(null); - done(); + ) + expect(token.uid).to.be.not.equal(null) + expect(token.preview_token).to.be.not.equal(null) + done() }) - .catch(done); - }); + .catch(done) + }) - it("should add a Preview Token", (done) => { + it('should add a Preview Token', (done) => { makePreviewToken(tokenUID) .create() .then((token) => { - expect(token.name).to.be.equal(createDeliveryToken3.token.name); + expect(token.name).to.be.equal(createDeliveryToken3.token.name) expect(token.description).to.be.equal( createDeliveryToken3.token.description - ); + ) expect(token.scope[0].environments[0].name).to.be.equal( createDeliveryToken3.token.scope[0].environments[0] - ); + ) expect(token.scope[0].module).to.be.equal( createDeliveryToken3.token.scope[0].module - ); - expect(token.uid).to.be.not.equal(null); - expect(token.preview_token).to.be.not.equal(null); - done(); + ) + expect(token.uid).to.be.not.equal(null) + expect(token.preview_token).to.be.not.equal(null) + done() }) - .catch(done); - }); + .catch(done) + }) - it("should delete a Preview Token from uid", (done) => { + it('should delete a Preview Token from uid', (done) => { makePreviewToken(tokenUID) .delete() .then((data) => { - expect(data.notice).to.be.equal("Preview token deleted successfully."); - done(); + expect(data.notice).to.be.equal('Preview token deleted successfully.') + done() }) - .catch(done); - }); + .catch(done) + }) - it("should delete a Delivery Token from uid", (done) => { + it('should delete a Delivery Token from uid', (done) => { makeDeliveryToken(tokenUID) .delete() .then((data) => { - expect(data.notice).to.be.equal("Delivery Token deleted successfully."); - done(); + expect(data.notice).to.be.equal('Delivery Token deleted successfully.') + done() }) - .catch(done); - }); -}); + .catch(done) + }) +}) -function makePreviewToken(uid = null) { +function makePreviewToken (uid = null) { return client .stack({ api_key: process.env.API_KEY }) .deliveryToken(uid) - .previewToken(); + .previewToken() } -function makeDeliveryToken(uid = null) { - return client.stack({ api_key: process.env.API_KEY }).deliveryToken(uid); +function makeDeliveryToken (uid = null) { + return client.stack({ api_key: process.env.API_KEY }).deliveryToken(uid) } diff --git a/test/sanity-check/mock/deliveryToken.js b/test/sanity-check/mock/deliveryToken.js index 1090d2c5..29ebc770 100644 --- a/test/sanity-check/mock/deliveryToken.js +++ b/test/sanity-check/mock/deliveryToken.js @@ -87,7 +87,7 @@ const createDeliveryToken3 = { { module: 'branch', branches: [ - 'main', + 'main' ], acl: { read: true From b70d2e49510f051cb7fba150ff356e0e9ea3b7e5 Mon Sep 17 00:00:00 2001 From: sunil-lakshman <104969541+sunil-lakshman@users.noreply.github.com> Date: Mon, 2 Jun 2025 18:09:26 +0530 Subject: [PATCH 4/5] Fixed security issues --- .talismanrc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.talismanrc b/.talismanrc index e784c309..632d61e1 100644 --- a/.talismanrc +++ b/.talismanrc @@ -17,3 +17,7 @@ fileignoreconfig: - filename: test/sanity-check/api/stack-test.js checksum: 198d5cf7ead33b079249dc3ecdee61a9c57453e93f1073ed0341400983e5aa53 version: "1.0" +fileignoreconfig: +- filename: test/sanity-check/api/previewToken-test.js + checksum: 9a42e079b7c71f76932896a0d2390d86ac626678ab20d36821dcf962820a886c +version: "1.0" From 0f268ec52474c3485f82a0973f947d66e9c4ffdd Mon Sep 17 00:00:00 2001 From: "harshitha.d" Date: Fri, 6 Jun 2025 17:30:26 +0530 Subject: [PATCH 5/5] Add deliveryToken files to Talisman configuration --- .talismanrc | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.talismanrc b/.talismanrc index 632d61e1..61cb65f1 100644 --- a/.talismanrc +++ b/.talismanrc @@ -20,4 +20,8 @@ version: "1.0" fileignoreconfig: - filename: test/sanity-check/api/previewToken-test.js checksum: 9a42e079b7c71f76932896a0d2390d86ac626678ab20d36821dcf962820a886c +- filename: lib/stack/deliveryToken/index.js + checksum: 51ae00f07f4cc75c1cd832b311c2e2482f04a8467a0139da6013ceb88fbdda2f +- filename: lib/stack/deliveryToken/previewToken/index.js + checksum: b506f33bffdd20dfc701f964370707f5d7b28a2c05c70665f0edb7b3c53c165b version: "1.0"