Skip to content

Commit de84c07

Browse files
Merge pull request #110 from contentstack/development
DX | 01-06-2026 | Release
2 parents f5cd4db + 6ce1be0 commit de84c07

16 files changed

Lines changed: 317 additions & 221 deletions

File tree

.cursor/rules/README.md

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
# Cursor (optional)
2+
3+
**Cursor** users: start at **[AGENTS.md](../../AGENTS.md)**. All conventions live in **`skills/*/SKILL.md`**.
4+
5+
This folder only points contributors to **`AGENTS.md`** so editor-specific config does not duplicate the canonical docs.

.cursor/rules/dev-workflow.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ Use this as the standard workflow when contributing to the Android CDA SDK.
55
## Branches
66

77
- Use feature branches for changes (e.g. `feat/...`, `fix/...`).
8-
- Base work off the appropriate long-lived branch (e.g. `staging`, `development`) per team norms.
8+
- Base work off the appropriate long-lived branch (e.g. `development`) per team norms.
99

1010
## Running tests
1111

Lines changed: 54 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
name: Back-merge master to development
2+
3+
on:
4+
push:
5+
branches:
6+
- master
7+
workflow_dispatch:
8+
9+
permissions:
10+
contents: read
11+
pull-requests: write
12+
13+
jobs:
14+
open-back-merge-pr:
15+
runs-on: ubuntu-latest
16+
steps:
17+
- name: Checkout
18+
uses: actions/checkout@v4
19+
with:
20+
fetch-depth: 0
21+
22+
- name: Open back-merge PR if needed
23+
env:
24+
GH_TOKEN: ${{ github.token }}
25+
run: |
26+
set -euo pipefail
27+
BASE_BRANCH="development"
28+
SOURCE_BRANCH="master"
29+
30+
git fetch origin "$BASE_BRANCH" "$SOURCE_BRANCH"
31+
32+
if ! git show-ref --verify --quiet "refs/remotes/origin/$BASE_BRANCH"; then
33+
echo "Base branch '$BASE_BRANCH' does not exist on origin; skipping."
34+
exit 0
35+
fi
36+
37+
SOURCE_SHA=$(git rev-parse "origin/$SOURCE_BRANCH")
38+
BASE_SHA=$(git rev-parse "origin/$BASE_BRANCH")
39+
40+
if [ "$SOURCE_SHA" = "$BASE_SHA" ]; then
41+
echo "$SOURCE_BRANCH and $BASE_BRANCH are at the same commit; nothing to back-merge."
42+
exit 0
43+
fi
44+
45+
EXISTING=$(gh pr list --repo "${{ github.repository }}" --base "$BASE_BRANCH" --head "$SOURCE_BRANCH" --state open --json number --jq 'length')
46+
47+
if [ "$EXISTING" -gt 0 ]; then
48+
echo "An open PR from $SOURCE_BRANCH to $BASE_BRANCH already exists; skipping."
49+
exit 0
50+
fi
51+
52+
gh pr create --repo "${{ github.repository }}" --base "$BASE_BRANCH" --head "$SOURCE_BRANCH" --title "chore: back-merge $SOURCE_BRANCH into $BASE_BRANCH" --body "Automated back-merge after changes landed on \\`$SOURCE_BRANCH\\`. Review and merge to keep \\`$BASE_BRANCH\\` in sync."
53+
54+
echo "Created back-merge PR $SOURCE_BRANCH -> $BASE_BRANCH."

.github/workflows/check-branch.yml

Lines changed: 0 additions & 20 deletions
This file was deleted.
Lines changed: 86 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,86 @@
1+
name: Check Version Bump
2+
3+
on:
4+
pull_request:
5+
6+
jobs:
7+
version-bump:
8+
name: Version & Changelog bump
9+
runs-on: ubuntu-latest
10+
steps:
11+
- name: Checkout
12+
uses: actions/checkout@v4
13+
with:
14+
fetch-depth: 0
15+
16+
- name: Detect changed files and version bump
17+
id: detect
18+
run: |
19+
if git rev-parse HEAD^2 >/dev/null 2>&1; then
20+
FILES=$(git diff --name-only HEAD^1 HEAD^2)
21+
else
22+
FILES=$(git diff --name-only HEAD~1 HEAD)
23+
fi
24+
VERSION_FILES_CHANGED=false
25+
echo "$FILES" | grep -qx 'package.json' && VERSION_FILES_CHANGED=true
26+
echo "$FILES" | grep -qx 'CHANGELOG.md' && VERSION_FILES_CHANGED=true
27+
echo "version_files_changed=$VERSION_FILES_CHANGED" >> $GITHUB_OUTPUT
28+
# Only lib/, webpack/, dist/, package.json count as release-affecting; .github/ and test/ do not
29+
CODE_CHANGED=false
30+
echo "$FILES" | grep -qE '^lib/|^webpack/|^dist/' && CODE_CHANGED=true
31+
echo "$FILES" | grep -qx 'package.json' && CODE_CHANGED=true
32+
echo "code_changed=$CODE_CHANGED" >> $GITHUB_OUTPUT
33+
34+
- name: Skip when only test/docs/.github changed
35+
if: steps.detect.outputs.code_changed != 'true'
36+
run: |
37+
echo "No release-affecting files changed (e.g. only test/docs/.github). Skipping version-bump check."
38+
exit 0
39+
40+
- name: Fail when version bump was missed
41+
if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed != 'true'
42+
run: |
43+
echo "::error::This PR has code changes but no version bump. Please bump the version in package.json and add an entry in CHANGELOG.md."
44+
exit 1
45+
46+
- name: Setup Node
47+
if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed == 'true'
48+
uses: actions/setup-node@v4
49+
with:
50+
node-version: '22.x'
51+
52+
- name: Check version bump
53+
if: steps.detect.outputs.code_changed == 'true' && steps.detect.outputs.version_files_changed == 'true'
54+
run: |
55+
set -e
56+
PKG_VERSION=$(node -p "require('./package.json').version.replace(/^v/, '')")
57+
if [ -z "$PKG_VERSION" ]; then
58+
echo "::error::Could not read version from package.json"
59+
exit 1
60+
fi
61+
git fetch --tags --force 2>/dev/null || true
62+
LATEST_TAG=$(git describe --tags --abbrev=0 2>/dev/null || true)
63+
if [ -z "$LATEST_TAG" ]; then
64+
echo "No existing tags found. Skipping version-bump check (first release)."
65+
exit 0
66+
fi
67+
LATEST_VERSION="${LATEST_TAG#v}"
68+
LATEST_VERSION="${LATEST_VERSION%%-*}"
69+
if [ "$(printf '%s\n' "$LATEST_VERSION" "$PKG_VERSION" | sort -V | tail -1)" != "$PKG_VERSION" ]; then
70+
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."
71+
exit 1
72+
fi
73+
if [ "$PKG_VERSION" = "$LATEST_VERSION" ]; then
74+
echo "::error::Version bump required: package.json version ($PKG_VERSION) equals latest tag ($LATEST_TAG). Please bump the version in package.json."
75+
exit 1
76+
fi
77+
CHANGELOG_VERSION=$(sed -nE 's/^## \[v?([0-9]+\.[0-9]+\.[0-9]+).*/\1/p' CHANGELOG.md | head -1)
78+
if [ -z "$CHANGELOG_VERSION" ]; then
79+
echo "::error::Could not find a version entry in CHANGELOG.md (expected line like '## [v1.0.0](...)')."
80+
exit 1
81+
fi
82+
if [ "$CHANGELOG_VERSION" != "$PKG_VERSION" ]; then
83+
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."
84+
exit 1
85+
fi
86+
echo "Version bump check passed: package.json and CHANGELOG.md are at $PKG_VERSION (latest tag: $LATEST_TAG)."

AGENTS.md

Lines changed: 33 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -1,54 +1,43 @@
1-
# Contentstack Android CDA SDK – Agent Guide
1+
# Contentstack Android Delivery SDK – Agent guide
22

3-
This document is the main entry point for AI agents working in this repository.
3+
**Universal entry point** for contributors and AI agents. Detailed conventions live in **`skills/*/SKILL.md`**.
44

5-
## Project
5+
## What this repo is
66

7-
- **Name:** Contentstack Android CDA SDK (contentstack-android)
8-
- **Purpose:** Android client for the Contentstack **Content Delivery API (CDA)**. It fetches content (entries, assets, content types, sync, etc.) from Contentstack for Android apps.
9-
- **Repo:** [contentstack-android](https://github.com/contentstack/contentstack-android)
7+
| Field | Detail |
8+
|--------|--------|
9+
| **Name:** | [contentstack-android](https://github.com/contentstack/contentstack-android) (`com.contentstack.sdk:android`) |
10+
| **Purpose:** | Android library for the Contentstack Content Delivery API (Kotlin/Java consumers via AAR). |
11+
| **Out of scope:** | Not the Java-only or iOS/Swift SDKs—those live in sibling repositories. |
1012

11-
## Tech stack
13+
## Tech stack (at a glance)
1214

13-
- **Languages:** Java (primary SDK source); Kotlin may appear in tests or future code. Target **Java 8** compatibility for the library (see `contentstack/build.gradle`).
14-
- **Build:** Gradle (Android Gradle Plugin), single module **`contentstack`** (AAR).
15-
- **Testing:** JUnit 4, Robolectric (unit tests on JVM), Mockito / PowerMock where used; **androidTest** with AndroidX Test / Espresso for instrumented tests; JaCoCo for coverage (`jacocoTestReport`).
16-
- **HTTP:** **Volley** (`CSHttpConnection`) for much of the CDA traffic; **Retrofit 2 + OkHttp + Gson** for paths such as taxonomy (`Stack`, `APIService`). OkHttp **MockWebServer** in unit tests.
15+
| Area | Details |
16+
|------|---------|
17+
| Language | Kotlin/Java; **compileSdk 34**; Java **17** compile options in `contentstack/build.gradle` |
18+
| Build | Gradle (root `build.gradle`, `settings.gradle`, module **`contentstack/`**) |
19+
| Tests | JUnit, Mockito, Robolectric, AndroidX Test—unit tests under `contentstack/src/test/` |
20+
| Lint / coverage | JaCoCo integrated with debug unit tests (`testCoverageEnabled` on debug) |
21+
| CI | `.github/workflows/check-branch.yml`, `publish-release.yml`, `sca-scan.yml`, `policy-scan.yml`, `codeql-analysis.yml` |
1722

18-
## Main entry points
23+
## Commands (quick reference)
1924

20-
- **`Contentstack`** – Factory: `Contentstack.stack(Context, apiKey, deliveryToken, environment)` (and overloads with `Config`) returns a **`Stack`**.
21-
- **`Stack`** – Main API: content types, entries, queries, assets, sync, etc.
22-
- **`Config`** – Optional configuration: host, version, region, branch, proxy, connection pool, endpoint.
23-
- **Paths:** `contentstack/src/main/java/com/contentstack/sdk/` (production), `contentstack/src/test/java/com/contentstack/sdk/` (unit tests), `contentstack/src/androidTest/java/` (instrumented tests).
25+
| Command type | Command |
26+
|--------------|---------|
27+
| Unit tests (typical) | `./gradlew :contentstack:testDebugUnitTest` (from repo root) |
28+
| Clean | `./gradlew clean` |
2429

25-
## Commands
30+
## Where the documentation lives: skills
2631

27-
Run from the **repository root** (requires Android SDK / `local.properties` for connected tests).
32+
| Skill | Path | What it covers |
33+
|-------|------|----------------|
34+
| **Development workflow** | [`skills/dev-workflow/SKILL.md`](skills/dev-workflow/SKILL.md) | Gradle, variants, CI, publishing |
35+
| **Android CDA SDK** | [`skills/contentstack-android-cda/SKILL.md`](skills/contentstack-android-cda/SKILL.md) | Library API and module boundaries |
36+
| **Android project layout** | [`skills/android/SKILL.md`](skills/android/SKILL.md) | `contentstack/` module, manifest, BuildConfig |
37+
| **Testing** | [`skills/testing/SKILL.md`](skills/testing/SKILL.md) | Unit vs instrumented tests, Robolectric, JaCoCo |
38+
| **Build & platform** | [`skills/framework/SKILL.md`](skills/framework/SKILL.md) | AGP, signing placeholders, `local.properties` |
39+
| **Code review** | [`skills/code-review/SKILL.md`](skills/code-review/SKILL.md) | PR checklist |
2840

29-
| Goal | Command |
30-
|------|---------|
31-
| **Build library (debug)** | `./gradlew :contentstack:assembleDebug` |
32-
| **Run all unit tests** | `./gradlew :contentstack:testDebugUnitTest` |
33-
| **Run instrumented / connected tests** | `./gradlew :contentstack:connectedDebugAndroidTest` (device or emulator required) |
34-
| **Unit + connected (full local test pass)** | `./gradlew :contentstack:testDebugUnitTest :contentstack:connectedDebugAndroidTest` |
35-
| **Coverage report (unit)** | `./gradlew :contentstack:jacocoTestReport` |
36-
37-
Instrumented tests may need **`local.properties`** entries (e.g. `APIKey`, `deliveryToken`, `environment`, `host`) for stacks that hit a real CDA endpoint—see `contentstack/build.gradle` `buildConfigField` usage.
38-
39-
## Rules and skills
40-
41-
- **`.cursor/rules/`** – Cursor rules for this repo:
42-
- **README.md** – Index of all rules (globs / always-on).
43-
- **dev-workflow.md** – Branches, tests, PR expectations.
44-
- **java.mdc** – Applies to `**/*.java` and `**/*.kt`: language style, `com.contentstack.sdk` layout, logging, null-safety.
45-
- **contentstack-android-cda.mdc** – SDK core: CDA patterns, Stack/Config, host/version/region/branch, retry, callbacks, CDA alignment.
46-
- **testing.mdc**`contentstack/src/test/**` and `contentstack/src/androidTest/**`: naming, unit vs instrumented, JaCoCo.
47-
- **code-review.mdc** – Always applied: PR/review checklist (aligned with Java CDA SDK).
48-
- **`skills/`** – Reusable skill docs:
49-
- **contentstack-android-cda** – Implementing or changing CDA behavior (Stack/Config, entries, assets, sync, HTTP, callbacks).
50-
- **testing** – Writing or refactoring tests.
51-
- **code-review** – PR review / pre-PR checklist.
52-
- **framework** – Config, HTTP layer (Volley + Retrofit/OkHttp), retry/timeouts.
53-
54-
Refer to `.cursor/rules/README.md` and `skills/README.md` for details.
41+
## Using Cursor (optional)
42+
43+
If you use **Cursor**, [`.cursor/rules/README.md`](.cursor/rules/README.md) only points to **`AGENTS.md`**—same docs as everyone else.

CHANGELOG.md

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

3+
## Version 4.2.2
4+
5+
### Date: 01-Jun-2026
6+
7+
- Fix: resolved data fetch failure for non-US regions (AZURE_NA, EU, AU, AZURE_EU, GCP_NA, GCP_EU) when connected via VPN by ensuring Stack.URL is correctly synced with the region-specific CDN host after config initialisation.
8+
39
## Version 4.2.1
410

511
### Date: 20-Apr-2026

contentstack/build.gradle

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ plugins {
77
ext {
88
PUBLISH_GROUP_ID = 'com.contentstack.sdk'
99
PUBLISH_ARTIFACT_ID = 'android'
10-
PUBLISH_VERSION = '4.2.1'
10+
PUBLISH_VERSION = '4.2.2'
1111
}
1212

1313
android {

contentstack/src/main/java/com/contentstack/sdk/Stack.java

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,6 +98,7 @@ protected void setConfig(Config config) {
9898
}
9999
}
100100
String endpoint = config.PROTOCOL + config.URL;
101+
URL = config.URL;
101102
this.config.setEndpoint(endpoint);
102103
client(endpoint);
103104

contentstack/src/test/java/com/contentstack/sdk/TestStack.java

Lines changed: 33 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,9 @@
1313
import org.robolectric.annotation.Config;
1414

1515
import java.util.Date;
16+
import java.util.HashMap;
1617
import java.util.LinkedHashMap;
18+
import java.util.Map;
1719

1820
import static org.junit.Assert.*;
1921

@@ -360,7 +362,7 @@ public void testStackWithCustomConfig() throws Exception {
360362
@Test
361363
public void testStackWithDifferentRegions() throws Exception {
362364
com.contentstack.sdk.Config.ContentstackRegion[] regions = com.contentstack.sdk.Config.ContentstackRegion.values();
363-
365+
364366
for (com.contentstack.sdk.Config.ContentstackRegion region : regions) {
365367
com.contentstack.sdk.Config config = new com.contentstack.sdk.Config();
366368
config.setRegion(region);
@@ -369,6 +371,36 @@ public void testStackWithDifferentRegions() throws Exception {
369371
}
370372
}
371373

374+
@Test
375+
public void testAzureNaRegionSetsCorrectURL() throws Exception {
376+
com.contentstack.sdk.Config config = new com.contentstack.sdk.Config();
377+
config.setRegion(com.contentstack.sdk.Config.ContentstackRegion.AZURE_NA);
378+
Stack regionalStack = Contentstack.stack(mockContext, "api_key", "token", "env", config);
379+
assertEquals("azure-na-cdn.contentstack.com", regionalStack.URL);
380+
}
381+
382+
@Test
383+
public void testNonUsRegionsSetsCorrectStackURL() throws Exception {
384+
Map<com.contentstack.sdk.Config.ContentstackRegion, String> expectedHosts = new HashMap<>();
385+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.EU, "eu-cdn.contentstack.io");
386+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.AU, "au-cdn.contentstack.com");
387+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.AZURE_NA, "azure-na-cdn.contentstack.com");
388+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.AZURE_EU, "azure-eu-cdn.contentstack.com");
389+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.GCP_NA, "gcp-na-cdn.contentstack.com");
390+
expectedHosts.put(com.contentstack.sdk.Config.ContentstackRegion.GCP_EU, "gcp-eu-cdn.contentstack.com");
391+
392+
for (Map.Entry<com.contentstack.sdk.Config.ContentstackRegion, String> entry : expectedHosts.entrySet()) {
393+
com.contentstack.sdk.Config config = new com.contentstack.sdk.Config();
394+
config.setRegion(entry.getKey());
395+
Stack regionalStack = Contentstack.stack(mockContext, "api_key", "token", "env", config);
396+
assertEquals(
397+
"Stack.URL mismatch for region " + entry.getKey(),
398+
entry.getValue(),
399+
regionalStack.URL
400+
);
401+
}
402+
}
403+
372404
@Test
373405
public void testContentTypeEntryCreation() {
374406
ContentType contentType = stack.contentType("products");

0 commit comments

Comments
 (0)