Skip to content

Commit 8a153cf

Browse files
Merge pull request #515 from contentstack/fix/branch-strategy-phase1
Fix/branch strategy phase1
2 parents c2d4562 + 3085d13 commit 8a153cf

File tree

12 files changed

+2203
-1778
lines changed

12 files changed

+2203
-1778
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:

.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: 5 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,6 @@
11
fileignoreconfig:
2-
- filename: lib/core/pkceStorage.js
3-
checksum: e060690c5ed348a6914df7ecc36de5b6b45f9a7c3a9c164c88bd2c7fad2bea08
4-
- filename: test/unit/oauthHandler-test.js
5-
checksum: 95a968c0d72d5bbe9e1acb30ea17ab505938f6174e917d7a25dda8facfda5a49
6-
- filename: test/unit/pkceStorage-test.js
7-
checksum: 567f557d37e8119c22cd4c5c4014c16dd660c03be35f65e803fb340cfd4b2136
2+
- filename: lib/contentstackClient.js
3+
checksum: f564f6eee5c17dc73abdeab4be226a3b37942893e149d907d2a4ef415c485c5e
84
- filename: test/unit/globalField-test.js
95
checksum: 25185e3400a12e10a043dc47502d8f30b7e1c4f2b6b4d3b8b55cdc19850c48bf
106
- filename: lib/stack/index.js
@@ -15,7 +11,9 @@ fileignoreconfig:
1511
ignore_detectors:
1612
- filecontent
1713
- filename: package-lock.json
18-
checksum: 47d7cb6b4cd8701aa289aa2e3975162b1291d4c8a60725d21c103a1ba59df201
14+
checksum: 4a58eb4ee1f54d68387bd005fb76e83a02461441c647d94017743d3442c0f476
15+
- filename: test/unit/ContentstackClient-test.js
16+
checksum: 5d8519b5b93c715e911a62b4033614cc4fb3596eabf31c7216ecb4cc08604a73
1917
- filename: .husky/pre-commit
2018
checksum: 52a664f536cf5d1be0bea19cb6031ca6e8107b45b6314fe7d47b7fad7d800632
2119
- filename: test/sanity-check/api/user-test.js
@@ -32,10 +30,6 @@ fileignoreconfig:
3230
checksum: e8a32ffbbbdba2a15f3d327273f0a5b4eb33cf84cd346562596ab697125bbbc6
3331
- filename: test/sanity-check/api/bulkOperation-test.js
3432
checksum: f40a14c84ab9a194aaf830ca68e14afde2ef83496a07d4a6393d7e0bed15fb0e
35-
- filename: lib/contentstackClient.js
36-
checksum: b76ca091caa3a1b2658cd422a2d8ef3ac9996aea0aff3f982d56bb309a3d9fde
37-
- filename: test/unit/ContentstackClient-test.js
38-
checksum: 974a4f335aef025b657d139bb290233a69bed1976b947c3c674e97baffe4ce2f
3933
- filename: test/unit/ContentstackHTTPClient-test.js
4034
checksum: 4043efd843e24da9afd0272c55ef4b0432e3374b2ca12b913f1a6654df3f62be
4135
- filename: test/unit/contentstack-test.js

.vscode/settings.json

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
{
2+
"snyk.advanced.autoSelectOrganization": false
3+
}

CHANGELOG.md

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,20 @@
11
# Changelog
22

3-
## [v1.27.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.6) (2026-03-02)
4-
- Enhancement
5-
- Added support for get job item status.
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
66

7-
## [v1.27.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-16)
8-
- Enhancement
9-
- OAuth PKCE: store code_verifier in sessionStorage (browser only) so token exchange works after redirect in React and other SPAs; verifier is restored on callback, cleared after successful exchange or on error; 10-minute expiry; Node remains memory-only
10-
- Extracted PKCE storage into `lib/core/pkceStorage.js` module
7+
## [v1.27.6](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-23)
118
- Fix
129
- 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
1310
- 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
1411

12+
## [v1.27.5](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.5) (2026-02-11)
13+
- Fix
14+
- 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
15+
- Hardened `responseHandler` to safely handle errors without `config` (e.g. plugin-replaced errors) by guarding `config.onComplete` and still running queue `shift()` so rejections remain catchable
16+
- Added optional chaining for `error.config` reads in the retry path and unit tests for missing-config scenarios
17+
1518
## [v1.27.4](https://github.com/contentstack/contentstack-management-javascript/tree/v1.27.4) (2026-02-02)
1619
- Fix
1720
- Removed content-type header from the release delete method

lib/core/concurrency-queue.js

Lines changed: 36 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -172,7 +172,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
172172
logFinalFailure(errorInfo, this.config.maxNetworkRetries)
173173
// Final error message
174174
const finalError = new Error(`Network request failed after ${this.config.maxNetworkRetries} retries: ${errorInfo.reason}`)
175-
finalError.code = error.code
175+
finalError.code = error && error.code
176176
finalError.originalError = error
177177
finalError.retryAttempts = attempt - 1
178178
return Promise.reject(finalError)
@@ -181,6 +181,16 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
181181
const delay = calculateNetworkRetryDelay(attempt)
182182
logRetryAttempt(errorInfo, attempt, delay)
183183

184+
// Guard: retry failures (e.g. from nested retries) may not have config in some
185+
// environments. Reject with a catchable error instead of throwing TypeError.
186+
if (!error || !error.config) {
187+
const finalError = new Error(`Network request failed after retries: ${errorInfo.reason}`)
188+
finalError.code = error && error.code
189+
finalError.originalError = error
190+
finalError.retryAttempts = attempt - 1
191+
return Promise.reject(finalError)
192+
}
193+
184194
// Initialize retry count if not present
185195
if (!error.config.networkRetryCount) {
186196
error.config.networkRetryCount = 0
@@ -200,9 +210,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
200210
safeAxiosRequest(requestConfig)
201211
.then((response) => {
202212
// On successful retry, call the original onComplete to properly clean up
203-
if (error.config.onComplete) {
204-
error.config.onComplete()
205-
}
213+
error?.config?.onComplete?.()
206214
shift() // Process next queued request
207215
resolve(response)
208216
})
@@ -214,17 +222,13 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
214222
.then(resolve)
215223
.catch((finalError) => {
216224
// On final failure, clean up the running queue
217-
if (error.config.onComplete) {
218-
error.config.onComplete()
219-
}
225+
error?.config?.onComplete?.()
220226
shift() // Process next queued request
221227
reject(finalError)
222228
})
223229
} else {
224230
// On non-retryable error, clean up the running queue
225-
if (error.config.onComplete) {
226-
error.config.onComplete()
227-
}
231+
error?.config?.onComplete?.()
228232
shift() // Process next queued request
229233
reject(retryError)
230234
}
@@ -446,9 +450,12 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
446450
}
447451
})
448452
}
449-
// Response interceptor used for
453+
// Response interceptor used for success and for error path (Promise.reject(responseHandler(err))).
454+
// When used with an error, err may lack config (e.g. plugin returns new error). Guard so we don't throw.
450455
const responseHandler = (response) => {
451-
response.config.onComplete()
456+
if (response?.config?.onComplete) {
457+
response.config.onComplete()
458+
}
452459
shift()
453460
return response
454461
}
@@ -478,13 +485,27 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
478485
}
479486

480487
const responseErrorHandler = error => {
481-
let networkError = error.config.retryCount
488+
// Guard: Axios errors normally have config; missing config can occur when a retry
489+
// fails in certain environments or when non-Axios errors propagate (e.g. timeouts).
490+
// Reject with a catchable error instead of throwing TypeError and crashing the process.
491+
if (!error || !error.config) {
492+
const fallbackError = new Error(
493+
error && typeof error.message === 'string'
494+
? error.message
495+
: 'Network request failed: error object missing request config'
496+
)
497+
fallbackError.code = error?.code
498+
fallbackError.originalError = error
499+
return Promise.reject(runPluginOnResponseForError(fallbackError))
500+
}
501+
502+
let networkError = error?.config?.retryCount ?? 0
482503
let retryErrorType = null
483504

484505
// First, check for transient network errors
485506
const networkErrorInfo = isTransientNetworkError(error)
486507
if (networkErrorInfo && this.config.retryOnNetworkFailure) {
487-
const networkRetryCount = error.config.networkRetryCount || 0
508+
const networkRetryCount = error?.config?.networkRetryCount || 0
488509
return retryNetworkError(error, networkErrorInfo, networkRetryCount + 1)
489510
}
490511

@@ -499,7 +520,7 @@ export function ConcurrencyQueue ({ axios, config, plugins = [] }) {
499520
var response = error.response
500521
if (!response) {
501522
if (error.code === 'ECONNABORTED') {
502-
const timeoutMs = error.config.timeout || this.config.timeout || 'unknown'
523+
const timeoutMs = error?.config?.timeout || this.config.timeout || 'unknown'
503524
error.response = {
504525
...error.response,
505526
status: 408,

0 commit comments

Comments
 (0)