Skip to content

Commit fa2a620

Browse files
Merge pull request #516 from contentstack/development
DX | 02-03-2026 | Release
2 parents 4f852bd + 445e313 commit fa2a620

File tree

20 files changed

+1012
-115
lines changed

20 files changed

+1012
-115
lines changed

.github/workflows/check-branch.yml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,13 @@ jobs:
88
runs-on: ubuntu-latest
99
steps:
1010
- name: Comment PR
11-
if: github.base_ref == 'master' && github.head_ref != 'staging'
11+
if: github.base_ref == 'master' && github.head_ref != 'developement'
1212
uses: thollander/actions-comment-pull-request@v2
1313
with:
1414
message: |
15-
We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch.
15+
We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch.
1616
- name: Check branch
17-
if: github.base_ref == 'master' && github.head_ref != 'staging'
17+
if: github.base_ref == 'master' && github.head_ref != 'development'
1818
run: |
19-
echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the next branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch."
19+
echo "ERROR: We regret to inform you that you are currently not able to merge your changes into the master branch due to restrictions applied by our SRE team. To proceed with merging your changes, we kindly request that you create a pull request from the development branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch."
2020
exit 1
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
# Ensures package.json and CHANGELOG.md are bumped compared to the latest tag when relevant files change.
2+
name: Check Version Bump
3+
4+
on:
5+
pull_request:
6+
paths:
7+
- 'package.json'
8+
- 'CHANGELOG.md'
9+
10+
jobs:
11+
version-bump:
12+
name: Version & Changelog bump
13+
runs-on: ubuntu-latest
14+
steps:
15+
- name: Checkout
16+
uses: actions/checkout@v4
17+
with:
18+
fetch-depth: 0
19+
20+
- name: Setup Node
21+
uses: actions/setup-node@v4
22+
with:
23+
node-version: '22.x'
24+
25+
- name: Check version bump
26+
run: |
27+
set -e
28+
PKG_VERSION=$(node -p "require('./package.json').version.replace(/^v/, '')")
29+
if [ -z "$PKG_VERSION" ]; then
30+
echo "::error::Could not read version from package.json"
31+
exit 1
32+
fi
33+
git fetch --tags --force 2>/dev/null || true
34+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)
35+
if [ -z "$LATEST_TAG" ]; then
36+
echo "No existing tags found. Skipping version-bump check (first release)."
37+
exit 0
38+
fi
39+
LATEST_VERSION="${LATEST_TAG#v}"
40+
LATEST_VERSION="${LATEST_VERSION%%-*}"
41+
if [ "$(printf '%s\n' "$LATEST_VERSION" "$PKG_VERSION" | sort -V | tail -1)" != "$PKG_VERSION" ]; then
42+
echo "::error::Version bump required: package.json version ($PKG_VERSION) is not greater than latest tag ($LATEST_TAG). Please bump the version in package.json."
43+
exit 1
44+
fi
45+
if [ "$PKG_VERSION" = "$LATEST_VERSION" ]; then
46+
echo "::error::Version bump required: package.json version ($PKG_VERSION) equals latest tag ($LATEST_TAG). Please bump the version in package.json."
47+
exit 1
48+
fi
49+
CHANGELOG_VERSION=$(sed -nE 's/^## \[v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' CHANGELOG.md | head -1)
50+
if [ -z "$CHANGELOG_VERSION" ]; then
51+
echo "::error::Could not find a version entry in CHANGELOG.md (expected line like '## [v1.0.0](...)')."
52+
exit 1
53+
fi
54+
if [ "$CHANGELOG_VERSION" != "$PKG_VERSION" ]; then
55+
echo "::error::CHANGELOG version mismatch: CHANGELOG.md top version ($CHANGELOG_VERSION) does not match package.json version ($PKG_VERSION). Please add or update the CHANGELOG entry for $PKG_VERSION."
56+
exit 1
57+
fi
58+
echo "Version bump check passed: package.json and CHANGELOG.md are at $PKG_VERSION (latest tag: $LATEST_TAG)."

.github/workflows/unit-test.yml

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ on:
44
branches:
55
- master
66
- main
7-
- staging
87
- development
98
jobs:
109
build-test:

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -71,3 +71,4 @@ jsdocs
7171
.early.coverage
7272
# Snyk Security Extension - AI Rules (auto-generated)
7373
.cursor/rules/snyk_rules.mdc
74+
.vscode/settings.json

.husky/post-checkout

Lines changed: 40 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
#!/usr/bin/env sh
2+
# When switching to a branch that doesn't exist on remote (e.g. newly created),
3+
# pull and merge origin/main or origin/master into current branch. Does not push.
4+
5+
# Only run on branch checkout (not file checkout)
6+
if [ "$3" != "1" ]; then
7+
exit 0
8+
fi
9+
10+
# Skip if we don't have a remote
11+
if ! git rev-parse --verify origin 2>/dev/null; then
12+
exit 0
13+
fi
14+
15+
CURRENT_BRANCH=$(git rev-parse --abbrev-ref HEAD)
16+
17+
# Skip main/master/development - no need to merge base into these
18+
case "$CURRENT_BRANCH" in
19+
main|master|development) exit 0 ;;
20+
esac
21+
22+
# Only run when current branch does not exist on origin (treat as new local branch)
23+
if git ls-remote --heads origin "$CURRENT_BRANCH" 2>/dev/null | grep -q .; then
24+
echo "post-checkout: $CURRENT_BRANCH exists on origin, skipping merge."
25+
exit 0
26+
fi
27+
28+
# Prefer main, fallback to master
29+
if git rev-parse --verify origin/main 2>/dev/null; then
30+
BASE=origin/main
31+
elif git rev-parse --verify origin/master 2>/dev/null; then
32+
BASE=origin/master
33+
else
34+
exit 0
35+
fi
36+
37+
echo "New branch detected: merging latest $BASE into $CURRENT_BRANCH (local only, not pushing)..."
38+
git fetch origin
39+
git merge "$BASE" --no-edit --no-ff
40+
echo "Done. Merge is local only; push when ready."

CHANGELOG.md

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

3+
## [v1.28.0](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-03-02)
4+
- Enh
5+
- Added DAM 2.0 query support
6+
37
## [v1.27.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-23)
48
- Fix
59
- Skip token refresh on 401 when API returns error_code 161 (environment/permission) so the actual API error is returned instead of triggering refresh and a generic "Unable to refresh token" message

README.md

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,6 +71,39 @@ contentstackClient.stack({ api_key: 'API_KEY', management_token: 'MANAGEMENT_TOK
7171
console.log(contenttype)
7272
})
7373
```
74+
75+
### Host and Region Configuration
76+
You can configure the SDK to use a specific region or custom host for API requests.
77+
78+
#### Region
79+
The SDK supports multiple regions. Valid region values are: `NA`, `EU`, `AU`, `AZURE_NA`, `AZURE_EU`, `GCP_NA`, `GCP_EU`. The default region is `NA`.
80+
81+
```javascript
82+
// Use EU region
83+
contentstackClient = contentstack.client({
84+
authtoken: 'AUTHTOKEN',
85+
region: 'EU'
86+
})
87+
```
88+
89+
#### Custom Host
90+
You can specify a custom host for API requests. If both `host` and `region` are provided, the `host` parameter takes priority.
91+
92+
```javascript
93+
// Use custom host
94+
contentstackClient = contentstack.client({
95+
authtoken: 'AUTHTOKEN',
96+
host: 'api.contentstack.io'
97+
})
98+
99+
// Custom host takes priority over region
100+
contentstackClient = contentstack.client({
101+
authtoken: 'AUTHTOKEN',
102+
region: 'EU',
103+
host: 'custom-api.example.com'
104+
})
105+
```
106+
74107
### Contentstack Management JavaScript SDK: 5-minute Quickstart
75108
#### Initializing Your SDK:
76109
To use the JavaScript CMA SDK, you need to first initialize it. To do this, use the following code:
@@ -124,7 +157,7 @@ contentstackClient.stack({ api_key: 'API_KEY' }).asset().create({ asset })
124157
- [Content Management API Docs](https://www.contentstack.com/docs/developers/apis/content-management-api)
125158

126159
### The MIT License (MIT)
127-
Copyright © 2012-2025 [Contentstack](https://www.contentstack.com/). All Rights Reserved
160+
Copyright © 2012-2026 [Contentstack](https://www.contentstack.com/). All Rights Reserved
128161

129162
Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
130163

lib/core/oauthHandler.js

Lines changed: 10 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import errorFormatter from './contentstackError'
22
import { ERROR_MESSAGES } from './errorMessages'
3+
import { getStoredCodeVerifier, storeCodeVerifier, clearStoredCodeVerifier } from './pkceStorage'
34

45
/**
56
* @description OAuthHandler class to handle OAuth authorization and token management
@@ -35,7 +36,13 @@ export default class OAuthHandler {
3536

3637
// Only generate PKCE codeVerifier and codeChallenge if clientSecret is not provided
3738
if (!this.clientSecret) {
38-
this.codeVerifier = this.generateCodeVerifier()
39+
const stored = getStoredCodeVerifier(this.appId, this.clientId, this.redirectUri)
40+
if (stored) {
41+
this.codeVerifier = stored
42+
} else {
43+
this.codeVerifier = this.generateCodeVerifier()
44+
storeCodeVerifier(this.appId, this.clientId, this.redirectUri, this.codeVerifier)
45+
}
3946
this.codeChallenge = null
4047
}
4148
}
@@ -139,8 +146,10 @@ export default class OAuthHandler {
139146
const response = await this.axiosInstance.post(`${this.developerHubBaseUrl}/token`, body)
140147

141148
this._saveTokens(response.data)
149+
clearStoredCodeVerifier(this.appId, this.clientId, this.redirectUri) // Clear immediately after successful exchange to prevent replay
142150
return response.data
143151
} catch (error) {
152+
clearStoredCodeVerifier(this.appId, this.clientId, this.redirectUri) // Clear on error to prevent replay attacks
144153
errorFormatter(error)
145154
}
146155
}

lib/core/pkceStorage.js

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,68 @@
1+
/**
2+
* PKCE code_verifier persistence in sessionStorage for browser SPAs.
3+
* Survives OAuth redirects; not used in Node. RFC 7636 / OAuth 2.0 for Browser-Based Apps.
4+
*/
5+
6+
const PKCE_STORAGE_KEY_PREFIX = 'contentstack_oauth_pkce'
7+
const PKCE_STORAGE_EXPIRY_MS = 10 * 60 * 1000 // 10 minutes
8+
9+
function isBrowser () {
10+
return typeof window !== 'undefined' && typeof window.sessionStorage !== 'undefined'
11+
}
12+
13+
function getStorageKey (appId, clientId, redirectUri) {
14+
return `${PKCE_STORAGE_KEY_PREFIX}_${appId}_${clientId}_${redirectUri}`
15+
}
16+
17+
/**
18+
* @param {string} appId
19+
* @param {string} clientId
20+
* @param {string} redirectUri
21+
* @returns {string|null} code_verifier if valid and not expired, otherwise null
22+
*/
23+
export function getStoredCodeVerifier (appId, clientId, redirectUri) {
24+
if (!isBrowser()) return null
25+
try {
26+
const raw = window.sessionStorage.getItem(getStorageKey(appId, clientId, redirectUri))
27+
if (!raw) return null
28+
const { codeVerifier, expiresAt } = JSON.parse(raw)
29+
if (!codeVerifier || !expiresAt || Date.now() > expiresAt) return null
30+
return codeVerifier
31+
} catch {
32+
return null
33+
}
34+
}
35+
36+
/**
37+
* @param {string} appId
38+
* @param {string} clientId
39+
* @param {string} redirectUri
40+
* @param {string} codeVerifier
41+
*/
42+
export function storeCodeVerifier (appId, clientId, redirectUri, codeVerifier) {
43+
if (!isBrowser()) return
44+
try {
45+
const key = getStorageKey(appId, clientId, redirectUri)
46+
const value = JSON.stringify({
47+
codeVerifier,
48+
expiresAt: Date.now() + PKCE_STORAGE_EXPIRY_MS
49+
})
50+
window.sessionStorage.setItem(key, value)
51+
} catch {
52+
// Ignore storage errors (e.g. private mode); fall back to memory-only
53+
}
54+
}
55+
56+
/**
57+
* @param {string} appId
58+
* @param {string} clientId
59+
* @param {string} redirectUri
60+
*/
61+
export function clearStoredCodeVerifier (appId, clientId, redirectUri) {
62+
if (!isBrowser()) return
63+
try {
64+
window.sessionStorage.removeItem(getStorageKey(appId, clientId, redirectUri))
65+
} catch {
66+
// Ignore
67+
}
68+
}

lib/stack/bulkOperation/index.js

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -126,6 +126,49 @@ export function BulkOperation (http, data = {}) {
126126
}
127127
}
128128

129+
/**
130+
* The getJobItems request allows you to get the items of a bulk job.
131+
* Response structure varies based on query params: items (always), skip/limit/total_count (when include_count=true), and other fields per params.
132+
* @memberof BulkOperation
133+
* @func getJobItems
134+
* @returns {Promise<Object>} Response Object. Structure varies with params - always includes items array; may include skip, limit, total_count when include_count=true.
135+
* @param {String} job_id - The ID of the job.
136+
* @param {Object} [params={}] - Query parameters. Supports: include_count, skip, limit, include_reference, status, type, ct (content type UID or array), api_version, and any other dynamic query params.
137+
* @example
138+
* client.stack({ api_key: 'api_key'}).bulkOperation().getJobItems('job_id')
139+
* .then((response) => { console.log(response) })
140+
* @example
141+
* client.stack({ api_key: 'api_key'}).bulkOperation().getJobItems('job_id', { skip: 0, limit: 50, include_count: true })
142+
* .then((response) => { console.log(response) })
143+
*/
144+
// eslint-disable-next-line camelcase
145+
this.getJobItems = async (job_id, params = {}) => {
146+
// eslint-disable-next-line camelcase
147+
const { api_version = '3.2', ...queryParams } = cloneDeep(params)
148+
// eslint-disable-next-line camelcase
149+
this.urlPath = `/bulk/jobs/${job_id}/items`
150+
const headers = {
151+
headers: {
152+
...cloneDeep(this.stackHeaders)
153+
}
154+
}
155+
// eslint-disable-next-line camelcase
156+
if (api_version) headers.headers.api_version = api_version
157+
if (Object.keys(queryParams).length > 0) headers.params = queryParams
158+
try {
159+
const response = await http.get(this.urlPath, headers)
160+
if (response.data) {
161+
// eslint-disable-next-line camelcase
162+
if (api_version) delete headers.headers.api_version
163+
return response.data
164+
}
165+
} catch (error) {
166+
// eslint-disable-next-line camelcase
167+
if (api_version) delete headers.headers.api_version
168+
console.error(error)
169+
}
170+
}
171+
129172
/**
130173
* The Publish entries and assets in bulk request allows you to publish multiple entries and assets at the same time.
131174
* @memberof BulkOperation

0 commit comments

Comments
 (0)