Skip to content

Commit 48ebc7f

Browse files
authored
Merge pull request #556 from contentstack/enh/dx-7264
Added branch support in entry variants
2 parents 3365429 + 8d966ca commit 48ebc7f

11 files changed

Lines changed: 575 additions & 21 deletions

File tree

CHANGELOG.md

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,14 @@
11
# Changelog
22

3+
## [v1.31.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.31.0) (2026-07-27)
4+
5+
- Enh
6+
- Entry variants: `contentType(...).entry(...).variants(variantUidOrUids, branchName?)` — optional second argument sets the CMA `branch` header for that variants scope (branch UID or alias). First argument accepts a variant UID string or an array of UIDs (comma-separated in the request path). Omitting `branchName` preserves previous behavior.
7+
- Entry variants: added `variants(uid).publish()` and `variants(uid).unpublish()`, calling the entry publish/unpublish endpoints with the variant payload nested under `entry`.
8+
- `publish()`/`unpublish()` (entry and entry variants) accept optional `headers` and `params`, merged into the underlying HTTP request.
9+
- Test
10+
- Unit tests and sanity API tests for entry variants with an explicit branch, and for the new variant `publish()`/`unpublish()` methods.
11+
312
## [v1.30.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.30.4) (2026-06-29)
413

514
- Update dependencies

lib/entity.js

Lines changed: 23 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ import ContentstackCollection from './contentstackCollection'
1313
* await publishFn.call(entryInstance, { publishDetails: {...}, locale: 'en-us' })
1414
*/
1515
export const publish = (http, type) => {
16-
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null }) {
16+
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null, headers: extraHeaders = {}, params = {} }) {
1717
const url = this.urlPath + '/publish'
1818
const headers = {
1919
headers: {
@@ -22,7 +22,10 @@ export const publish = (http, type) => {
2222
} || {}
2323
const httpBody = {}
2424
httpBody[type] = cloneDeep(publishDetails)
25-
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt)
25+
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt, {
26+
headers: extraHeaders,
27+
params
28+
})
2629
}
2730
}
2831

@@ -36,7 +39,7 @@ export const publish = (http, type) => {
3639
* await unpublishFn.call(entryInstance, { publishDetails: {...}, locale: 'en-us' })
3740
*/
3841
export const unpublish = (http, type) => {
39-
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null }) {
42+
return async function ({ publishDetails, locale = null, version = null, scheduledAt = null, headers: extraHeaders = {}, params = {} }) {
4043
const url = this.urlPath + '/unpublish'
4144
const headers = {
4245
headers: {
@@ -45,7 +48,10 @@ export const unpublish = (http, type) => {
4548
} || {}
4649
const httpBody = {}
4750
httpBody[type] = cloneDeep(publishDetails)
48-
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt)
51+
return publishUnpublish(http, url, httpBody, headers, locale, version, scheduledAt, {
52+
headers: extraHeaders,
53+
params
54+
})
4955
}
5056
}
5157

@@ -58,11 +64,12 @@ export const unpublish = (http, type) => {
5864
* @param {string|null} locale - Locale code.
5965
* @param {number|null} version - Version number.
6066
* @param {string|null} scheduledAt - Scheduled date/time in ISO format.
67+
* @param {Object} [configExtras={}] - Optional `{ headers, params }` merged into the axios request (stack headers stay in `headers.headers`).
6168
* @returns {Promise<Object>} Promise that resolves to response data.
6269
* @async
6370
* @private
6471
*/
65-
export const publishUnpublish = async (http, url, httpBody, headers, locale = null, version = null, scheduledAt = null) => {
72+
export const publishUnpublish = async (http, url, httpBody, headers, locale = null, version = null, scheduledAt = null, configExtras = {}) => {
6673
if (locale !== null) {
6774
httpBody.locale = locale
6875
}
@@ -72,8 +79,18 @@ export const publishUnpublish = async (http, url, httpBody, headers, locale = nu
7279
if (scheduledAt !== null) {
7380
httpBody.scheduled_at = scheduledAt
7481
}
82+
const requestConfig = {
83+
headers: {
84+
...cloneDeep(headers?.headers || {}),
85+
...cloneDeep(configExtras?.headers || {})
86+
},
87+
params: {
88+
...cloneDeep(headers?.params || {}),
89+
...cloneDeep(configExtras?.params || {})
90+
}
91+
}
7592
try {
76-
const response = await http.post(url, httpBody, headers)
93+
const response = await http.post(url, httpBody, requestConfig)
7794
if (response.data) {
7895
const data = response.data || {}
7996
if (http?.httpClientParams?.headers?.api_version) {

lib/stack/contentType/entry/index.js

Lines changed: 45 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -285,22 +285,60 @@ export function Entry (http, data) {
285285
* @description The variants call returns a Variants instance for managing variants of an entry.
286286
* @memberof Entry
287287
* @func variants
288-
* @param {String=} uid - Variant UID. If not provided, returns Variants instance for querying all variants.
288+
* @param {string|string[]=} variantUidOrUids - Variant UID, list of UIDs (comma-separated in the path), or omit to query all variants.
289+
* @param {string=} branchName - Optional branch UID or alias for this variants scope (sent as the branch header). Omit to use the stack default branch.
289290
* @returns {Variants} Instance of Variants.
290291
* @example
291292
* import * as contentstack from '@contentstack/management'
292293
* const client = contentstack.client()
293294
* const variants = client.stack({ api_key: 'api_key'}).contentType('content_type_uid').entry('uid').variants('uid')
294295
* variants.fetch()
295296
* .then((response) => console.log(response));
297+
* @example
298+
* client.stack({ api_key: 'api_key'}).contentType('content_type_uid').entry('uid').variants('uid', 'branch_name').update(data)
299+
* .then((response) => console.log(response));
296300
*/
297-
this.variants = (uid = null) => {
298-
const data = { stackHeaders: this.stackHeaders }
299-
data.content_type_uid = this.content_type_uid
300-
data.entry_uid = this.uid
301-
if (uid) {
302-
data.variants_uid = uid
301+
this.variants = (variantUidOrUids, branchName) => {
302+
const uidInput = variantUidOrUids === undefined ? null : variantUidOrUids
303+
const branch =
304+
typeof branchName === 'string' && branchName !== ''
305+
? branchName
306+
: undefined
307+
308+
const data = {
309+
content_type_uid: this.content_type_uid,
310+
entry_uid: this.uid
311+
}
312+
313+
if (branch === undefined) {
314+
data.stackHeaders = this.stackHeaders
315+
} else {
316+
data.stackHeaders = {
317+
...cloneDeep(this.stackHeaders || {}),
318+
branch
319+
}
303320
}
321+
322+
let variantsUid = null
323+
if (Array.isArray(uidInput)) {
324+
const uids = uidInput.filter(
325+
(uid) => typeof uid === 'string' && uid.length > 0
326+
)
327+
if (uids.length === 1) {
328+
variantsUid = uids[0]
329+
} else if (uids.length > 1) {
330+
variantsUid = uids.join(',')
331+
}
332+
} else if (typeof uidInput === 'string' && uidInput.length > 0) {
333+
variantsUid = uidInput
334+
} else if (uidInput != null && uidInput !== '') {
335+
variantsUid = uidInput
336+
}
337+
338+
if (variantsUid != null && variantsUid !== '') {
339+
data.variants_uid = variantsUid
340+
}
341+
304342
return new Variants(http, data)
305343
}
306344

lib/stack/contentType/entry/variants/index.js

Lines changed: 98 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,8 @@ import cloneDeep from 'lodash/cloneDeep'
22
import {
33
deleteEntity,
44
fetch,
5-
query
5+
query,
6+
publishUnpublish
67
}
78
from '../../../../entity'
89
import error from '../../../../core/contentstackError'
@@ -14,8 +15,19 @@ import { bindModuleHeaders } from '../../../../core/moduleHeaderSupport.js'
1415
export function Variants (http, data) {
1516
Object.assign(this, cloneDeep(data))
1617
this.urlPath = `/content_types/${this.content_type_uid}/entries/${this.entry_uid}/variants`
17-
if (data && data.variants_uid) {
18-
this.urlPath += `/${this.variants_uid}`
18+
let variantPathSegment = ''
19+
if (data?.variants_uid != null && data.variants_uid !== '') {
20+
if (Array.isArray(data.variants_uid)) {
21+
variantPathSegment = data.variants_uid
22+
.filter((uid) => typeof uid === 'string' && uid.length > 0)
23+
.join(',')
24+
} else {
25+
variantPathSegment = String(data.variants_uid)
26+
}
27+
}
28+
if (variantPathSegment) {
29+
this.urlPath += `/${variantPathSegment}`
30+
const entryBaseUrlPath = `/content_types/${this.content_type_uid}/entries/${this.entry_uid}`
1931
/**
2032
* @description The Update a variant call updates an existing variant for the selected content type.
2133
* @memberof Variants
@@ -38,7 +50,7 @@ export function Variants (http, data) {
3850
* }
3951
* }
4052
* }
41-
* client.stack({ api_key: 'api_key'}).contentType('content_type_uid').entry('entry_uid').variants('uid').update(data)
53+
* client.stack({ api_key: 'api_key'}).contentType('content_type_uid').entry('entry_uid').variants('uid', 'branch_name').update(data)
4254
* .then((variants) => console.log(variants))
4355
*/
4456
this.update = async (data, params = {}) => {
@@ -121,6 +133,88 @@ export function Variants (http, data) {
121133
return error(err)
122134
}
123135
}
136+
137+
/**
138+
* @description Publishes via the entry publish endpoint (POST .../entries/{entry_uid}/publish). Pass `publishDetails` as the object nested under `entry` (environments, locales, variants, variant_rules, etc.). Optional `headers` and `params` are merged into the HTTP request.
139+
* @memberof Variants
140+
* @func publish
141+
* @param {Object} options
142+
* @param {Object} options.publishDetails - Payload for the `entry` property (e.g. environments, locales, variants, variant_rules).
143+
* @param {String|null} [options.locale] - Top-level `locale` on the request body.
144+
* @param {Number|null} [options.version] - Top-level `version` on the request body.
145+
* @param {String|null} [options.scheduledAt] - Top-level `scheduled_at` (ISO) on the request body.
146+
* @param {Object} [options.headers={}] - Extra request headers merged with stack headers.
147+
* @param {Object} [options.params={}] - Query string parameters for the request.
148+
* @returns {Promise<Object>} Response data (e.g. notice, job_id).
149+
* @example
150+
* import * as contentstack from '@contentstack/management'
151+
* const client = contentstack.client()
152+
* await client.stack({ api_key: 'api_key' }).contentType('ct').entry('entry_uid').variants('variant_uid').publish({
153+
* publishDetails: {
154+
* environments: ['production'],
155+
* locales: ['en-us'],
156+
* variants: [{ uid: 'variant_uid', version: 1 }],
157+
* variant_rules: { publish_latest_base: false, publish_latest_base_conditionally: true }
158+
* },
159+
* locale: 'en-us'
160+
* })
161+
*/
162+
this.publish = async ({
163+
publishDetails,
164+
locale = null,
165+
version = null,
166+
scheduledAt = null,
167+
headers: extraHeaders = {},
168+
params = {}
169+
}) => {
170+
const url = `${entryBaseUrlPath}/publish`
171+
const httpBody = {}
172+
httpBody.entry = cloneDeep(publishDetails)
173+
const baseHeaders = {
174+
headers: {
175+
...cloneDeep(this.stackHeaders)
176+
}
177+
}
178+
return publishUnpublish(http, url, httpBody, baseHeaders, locale, version, scheduledAt, {
179+
headers: extraHeaders,
180+
params
181+
})
182+
}
183+
184+
/**
185+
* @description Unpublishes via the entry unpublish endpoint (POST .../entries/{entry_uid}/unpublish). Pass `publishDetails` as the object nested under `entry`. Optional `headers` and `params` are merged into the HTTP request.
186+
* @memberof Variants
187+
* @func unpublish
188+
* @param {Object} options
189+
* @param {Object} options.publishDetails - Payload for the `entry` property (e.g. environments, locales, variants).
190+
* @param {String|null} [options.locale] - Top-level `locale` on the request body.
191+
* @param {Number|null} [options.version] - Top-level `version` on the request body.
192+
* @param {String|null} [options.scheduledAt] - Top-level `scheduled_at` (ISO) on the request body.
193+
* @param {Object} [options.headers={}] - Extra request headers merged with stack headers.
194+
* @param {Object} [options.params={}] - Query string parameters for the request.
195+
* @returns {Promise<Object>} Response data (e.g. notice, job_id).
196+
*/
197+
this.unpublish = async ({
198+
publishDetails,
199+
locale = null,
200+
version = null,
201+
scheduledAt = null,
202+
headers: extraHeaders = {},
203+
params = {}
204+
}) => {
205+
const url = `${entryBaseUrlPath}/unpublish`
206+
const httpBody = {}
207+
httpBody.entry = cloneDeep(publishDetails)
208+
const baseHeaders = {
209+
headers: {
210+
...cloneDeep(this.stackHeaders)
211+
}
212+
}
213+
return publishUnpublish(http, url, httpBody, baseHeaders, locale, version, scheduledAt, {
214+
headers: extraHeaders,
215+
params
216+
})
217+
}
124218
} else {
125219
/**
126220
* @description The Query on Variants will allow you to fetch details of all or specific Variants.

package-lock.json

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@contentstack/management",
3-
"version": "1.30.4",
3+
"version": "1.31.0",
44
"description": "The Content Management API is used to manage the content of your Contentstack account",
55
"main": "./dist/node/contentstack-management.js",
66
"browser": "./dist/web/contentstack-management.js",

0 commit comments

Comments
 (0)