Skip to content

Commit 8d5bc32

Browse files
committed
Merge master + fix environment usage for downstream tests
- environment-test: use separate temp env for 'update name' test so shared development env is never renamed; bulk/entry/release/workflow keep using testData.environments.development - bulkOperation-test: use testData.environments.development.name (envName) instead of hardcoded 'development' for all publish/unpublish payloads - .talismanrc: update checksums for modified test files and merge artifacts - No sensitive data: only process.env.API_KEY and env var references
2 parents 0a53635 + a43c284 commit 8d5bc32

24 files changed

+1620
-711
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/sca-scan.yml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@ jobs:
1313
SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}
1414
with:
1515
args: --all-projects --fail-on=all
16+
json: true
17+
continue-on-error: true
18+
- uses: contentstack/sca-policy@main

.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: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -72,4 +72,7 @@ test-curls.txt
7272
dist
7373
jsdocs
7474
.early.coverage
75-
docs/
75+
docs/
76+
# Snyk Security Extension - AI Rules (auto-generated)
77+
.cursor/rules/snyk_rules.mdc
78+
.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."

.talismanrc

Lines changed: 16 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ fileignoreconfig:
99
ignore_detectors:
1010
- filecontent
1111
- filename: package-lock.json
12-
checksum: 92b88ce00603ede68344bac6bd6bf76bdb76f1e5f5ba8d1d0c79da2b72c5ecc0
12+
checksum: ff309d2a910e885853fb25faa6aa3d2bd2cfc2d316c84a0e984bbda79c596ae9
1313
- filename: test/unit/ContentstackClient-test.js
1414
checksum: 5d8519b5b93c715e911a62b4033614cc4fb3596eabf31c7216ecb4cc08604a73
1515
- filename: .husky/pre-commit
@@ -28,7 +28,7 @@ fileignoreconfig:
2828
checksum: 2597efae3c1ab8cc173d5bf205f1c76932211f8e0eb2a16444e055d83481976c
2929
# Sanity check test files - use process.env for all secrets (no hardcoded values)
3030
- filename: test/sanity-check/api/environment-test.js
31-
checksum: 91d76e6a2c4639db04071a30a9212df32777ab5f0e3a23dc101f4d62c13609b0
31+
checksum: 5c3225c9013d7f0640ed7ef0e556fda4382e52b93da1af1f155c94eededd5195
3232
- filename: test/sanity-check/env.example.txt
3333
checksum: 3339944cd20d6d72f70a92e54af3de96736250b4b7117a29577575f9b52ed611
3434
- filename: test/sanity-check/api/token-test.js
@@ -94,7 +94,7 @@ fileignoreconfig:
9494
- filename: test/sanity-check/api/contentType-test.js
9595
checksum: 4d5178998f9f3c27550c5bd21540e254e08f79616e8615e7256ba2175cb4c8e1
9696
- filename: test/sanity-check/api/bulkOperation-test.js
97-
checksum: 6281e14c7a10864c586e95139f47ae2ee5bb2322a2beaec166a1f6ece830431b
97+
checksum: 1721c0e0f5b79a46fba21bfbedc3a87db4715bc97cd064f2361d35ab453b890c
9898
- filename: test/sanity-check/api/entry-test.js
9999
checksum: 9dc16b404a98ff9fa2c164fad0182b291b9c338dd58558dc5ef8dd75cf18bc1f
100100
- filename: test/sanity-check/api/entryVariants-test.js
@@ -107,4 +107,17 @@ fileignoreconfig:
107107
checksum: 6c0d8f6e7c85cd2fa5f0a20e8a49e94df0dde1b2c1d7e9c39e8c9c6c8b8d5e2f1
108108
- filename: test/unit/concurrency-Queue-test.js
109109
checksum: fd5c327f4fa1b334fdb1a2d903ac0213752e7829f31f19667215aa186c3efbbf
110+
# From merge / Talisman allowlist (no secrets - env vars, test patterns, lockfile hashes)
111+
- filename: lib/core/pkceStorage.js
112+
checksum: e060690c5ed348a6914df7ecc36de5b6b45f9a7c3a9c164c88bd2c7fad2bea08
113+
- filename: lib/stack/bulkOperation/index.js
114+
checksum: c2eb400c6617a860bd5c519ace03c06cde169135e1c258106cfc3263dfcfc4d1
115+
- filename: test/unit/pkceStorage-test.js
116+
checksum: 567f557d37e8119c22cd4c5c4014c16dd660c03be35f65e803fb340cfd4b2136
117+
- filename: test/unit/oauthHandler-test.js
118+
checksum: 95a968c0d72d5bbe9e1acb30ea17ab505938f6174e917d7a25dda8facfda5a49
119+
- filename: types/stack/bulkOperation/index.d.ts
120+
checksum: d6771a51a021977ce9f428afc289c80aee3d2fca211f4102cf782afded354ce7
121+
- filename: .github/workflows/check-version-bump.yml
122+
checksum: c4aff7ca93f40213f67534e5c490d7a633cbebb21bbebd4517ff9b9db37ab52a
110123
version: "1.0"

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.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+
7+
## [v1.27.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-23)
8+
- Fix
9+
- 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
10+
- When token refresh fails after a 401, return the original API error (error_message, error_code) instead of the generic "Unable to refresh token" message
11+
312
## [v1.27.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-11)
413
- Fix
514
- Concurrency queue: when response errors have no `config` (e.g. after network retries exhaust in some environments, or when plugins return a new error object), the SDK now rejects with a catchable Error instead of throwing an unhandled TypeError and crashing the process

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/concurrency-queue.js

Lines changed: 32 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -404,16 +404,33 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
404404
this.config.authtoken = token.authtoken
405405
}
406406
}).catch((error) => {
407+
const apiError = this._last401ApiError
408+
if (apiError) {
409+
this._last401ApiError = null
410+
}
407411
this.queue.forEach(queueItem => {
408-
queueItem.reject({
409-
errorCode: '401',
410-
errorMessage: (error instanceof Error) ? error.message : error,
411-
code: 'Unauthorized',
412-
message: 'Unable to refresh token',
413-
name: 'Token Error',
414-
config: queueItem.request,
415-
stack: (error instanceof Error) ? error.stack : null
416-
})
412+
if (apiError) {
413+
queueItem.reject({
414+
errorCode: apiError.error_code ?? '401',
415+
errorMessage: apiError.error_message || apiError.message || ((error instanceof Error) ? error.message : String(error)),
416+
code: 'Unauthorized',
417+
message: apiError.error_message || apiError.message || 'Unable to refresh token',
418+
name: 'Token Error',
419+
config: queueItem.request,
420+
stack: (error instanceof Error) ? error.stack : null,
421+
response: { status: 401, statusText: 'Unauthorized', data: apiError }
422+
})
423+
} else {
424+
queueItem.reject({
425+
errorCode: '401',
426+
errorMessage: (error instanceof Error) ? error.message : error,
427+
code: 'Unauthorized',
428+
message: 'Unable to refresh token',
429+
name: 'Token Error',
430+
config: queueItem.request,
431+
stack: (error instanceof Error) ? error.stack : null
432+
})
433+
}
417434
})
418435
this.queue = []
419436
this.running = []
@@ -515,9 +532,11 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
515532
return Promise.reject(responseHandler(err))
516533
}
517534
} else if ((response.status === 401 && this.config.refreshToken)) {
518-
// If error_code is 294 (2FA required), don't retry/refresh - pass through the error as-is
535+
// Retry/refresh only for authentication-related 401s (e.g. token expiry). Do not retry
536+
// when the API returns a specific error_code for non-auth issues (2FA, permission, etc.).
519537
const apiErrorCode = response.data?.error_code
520-
if (apiErrorCode === 294) {
538+
const NON_AUTH_401_ERROR_CODES = new Set([294, 161]) // 294 = 2FA required, 161 = env/permission
539+
if (apiErrorCode !== undefined && apiErrorCode !== null && NON_AUTH_401_ERROR_CODES.has(apiErrorCode)) {
521540
const err = runPluginOnResponseForError(error)
522541
return Promise.reject(responseHandler(err))
523542
}
@@ -530,6 +549,8 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
530549
return Promise.reject(responseHandler(err))
531550
}
532551
this.running.shift()
552+
// Store original API error so we can return it if refresh fails (instead of generic message)
553+
this._last401ApiError = response.data
533554
// Cool down the running requests
534555
delay(wait, response.status === 401)
535556
error.config.retryCount = networkError

0 commit comments

Comments
 (0)