Skip to content

Commit fae6c72

Browse files
Merge pull request #30 from contentstack/staging
DX | 30-03-2026 | Release
2 parents 3914e22 + 50120e8 commit fae6c72

10 files changed

Lines changed: 531 additions & 7 deletions

File tree

.github/workflows/check-branch.yml

Lines changed: 5 additions & 5 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 != 'next'
11+
if: github.base_ref == 'master' && github.head_ref != 'staging'
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 staging 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 != 'next'
17+
if: github.base_ref == 'master' && github.head_ref != 'staging'
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."
20-
exit 1
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 staging branch. Our team will then review the changes and work with you to ensure a successful merge into the master branch."
20+
exit 1

CHANGELOG.md

Lines changed: 88 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,88 @@
1+
# Changelog
2+
3+
All notable changes to this project will be documented in this file.
4+
5+
## [1.4.0] - 2026-03-30
6+
7+
### Enhancement
8+
9+
- **Variant utility** — read variant aliases and emit `data-csvariants` JSON (single or many entries).
10+
- **`VariantUtilityError`** on invalid input.
11+
12+
## [1.3.4] - 2025-01-17
13+
14+
### Changed
15+
16+
- Deployment targets updated.
17+
- General enhancements and latest platform support.
18+
19+
## [1.3.3] - 2024-05-17
20+
21+
### Added
22+
23+
- Privacy manifest file.
24+
25+
### Changed
26+
27+
- Updated tests.
28+
29+
## [1.3.2] - 2024-03-28
30+
31+
### Added
32+
33+
- Support for the **fragment** tag in JSON RTE.
34+
35+
## [1.3.1] - 2023-11-20
36+
37+
### Fixed
38+
39+
- Image linking issue.
40+
41+
## [1.3.0] - 2023-05-26
42+
43+
### Added
44+
45+
- Nested asset support.
46+
- Break tag support.
47+
48+
## [1.2.1] - 2022-09-09
49+
50+
### Fixed
51+
52+
- Swift Package warning (exclude warning from package removed).
53+
54+
## [1.2.0] - 2021-08-10
55+
56+
### Added
57+
58+
- JSON RTE to HTML support for the **GQL API**.
59+
60+
## [1.1.2] - 2021-07-16
61+
62+
### Added
63+
64+
- JSON RTE content to HTML parsing support.
65+
66+
## [1.1.1] - 2021-04-09
67+
68+
### Changed
69+
70+
- Deployment target updates.
71+
72+
### Removed
73+
74+
- XC Framework (issue resolved).
75+
76+
## [1.1.0] - 2021-04-06
77+
78+
### Fixed
79+
80+
- Swift Package duplicate naming for ContentstackUtils.
81+
82+
## [1.0.0] - 2021-04-06
83+
84+
### Added
85+
86+
- Embedded items feature support.
87+
- `includeEmbeddedItems` in Entry and Query modules.
88+
- Utils SDK support in the SDK.

ContentstackUtils.xcodeproj/project.pbxproj

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,9 @@
6565
0FFF2F2A2668FC54003E9DBF /* NodeType.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F292668FC54003E9DBF /* NodeType.swift */; };
6666
0FFF2F382668FE85003E9DBF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0FFF2F372668FE85003E9DBF /* Node.swift */; };
6767
64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */ = {isa = PBXBuildFile; fileRef = 64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */; };
68+
6749AC902F714E26007282C5 /* variantsEntries.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC8F2F714E26007282C5 /* variantsEntries.json */; };
69+
6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */ = {isa = PBXBuildFile; fileRef = 6749AC912F714E2F007282C5 /* variantsSingleEntry.json */; };
70+
6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6749AC932F714E36007282C5 /* VariantUtilityTests.swift */; };
6871
OBJ_22 /* ContentstackUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_9 /* ContentstackUtils.swift */; };
6972
OBJ_29 /* Package.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_6 /* Package.swift */; };
7073
OBJ_40 /* ContentstackUtilsTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = OBJ_12 /* ContentstackUtilsTests.swift */; };
@@ -138,6 +141,10 @@
138141
0FFF2F292668FC54003E9DBF /* NodeType.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NodeType.swift; sourceTree = "<group>"; };
139142
0FFF2F372668FE85003E9DBF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
140143
64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */ = {isa = PBXFileReference; lastKnownFileType = text.xml; path = PrivacyInfo.xcprivacy; sourceTree = "<group>"; };
144+
6749AC8F2F714E26007282C5 /* variantsEntries.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsEntries.json; sourceTree = "<group>"; };
145+
6749AC912F714E2F007282C5 /* variantsSingleEntry.json */ = {isa = PBXFileReference; lastKnownFileType = text.json; path = variantsSingleEntry.json; sourceTree = "<group>"; };
146+
6749AC932F714E36007282C5 /* VariantUtilityTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VariantUtilityTests.swift; sourceTree = "<group>"; };
147+
6749AC952F715507007282C5 /* CHANGELOG.md */ = {isa = PBXFileReference; lastKnownFileType = net.daringfireball.markdown; path = CHANGELOG.md; sourceTree = "<group>"; };
141148
"ContentstackUtils::ContentstackUtils::Product" /* ContentstackUtils.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; path = ContentstackUtils.framework; sourceTree = BUILT_PRODUCTS_DIR; };
142149
"ContentstackUtils::ContentstackUtilsTests::Product" /* ContentstackUtilsTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; path = ContentstackUtilsTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; };
143150
OBJ_12 /* ContentstackUtilsTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContentstackUtilsTests.swift; sourceTree = "<group>"; };
@@ -321,6 +328,9 @@
321328
0F07E62E25244DB5003E0BD1 /* StringExtensionTests.swift */,
322329
0F5E484B2525DB600038C16B /* TestClient.swift */,
323330
0FEC0B3A254FEC60008D4E66 /* MetadataTests.swift */,
331+
6749AC8F2F714E26007282C5 /* variantsEntries.json */,
332+
6749AC912F714E2F007282C5 /* variantsSingleEntry.json */,
333+
6749AC932F714E36007282C5 /* VariantUtilityTests.swift */,
324334
0F7142C325514A6F00C18A61 /* ContentstackUtilsArrayTest.swift */,
325335
0F7142C52551684600C18A61 /* ContentstackUtilsCustomRendertest.swift */,
326336
0F579540266A50D40082815C /* MarkTypeTest.swift */,
@@ -347,6 +357,7 @@
347357
64F522122BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy */,
348358
0FAA3EBD26A1C65B00173FA9 /* ContentstackUtils.podspec */,
349359
OBJ_6 /* Package.swift */,
360+
6749AC952F715507007282C5 /* CHANGELOG.md */,
350361
0F7142C725517A4900C18A61 /* README.md */,
351362
0FA3D58E252228E300E58179 /* Scripts */,
352363
OBJ_7 /* Sources */,
@@ -471,7 +482,9 @@
471482
isa = PBXResourcesBuildPhase;
472483
buildActionMask = 2147483647;
473484
files = (
485+
6749AC922F714E2F007282C5 /* variantsSingleEntry.json in Resources */,
474486
64F522132BF5F3F300AE6E0F /* PrivacyInfo.xcprivacy in Resources */,
487+
6749AC902F714E26007282C5 /* variantsEntries.json in Resources */,
475488
0F5E484E2525DDD70038C16B /* EntryEmbedded.json in Resources */,
476489
);
477490
runOnlyForDeploymentPostprocessing = 0;
@@ -556,6 +569,7 @@
556569
0FFD88D7266DDD1900BA5919 /* ContentstackUtilsJsonToHtmlTest.swift in Sources */,
557570
0F07E62F25244DB5003E0BD1 /* StringExtensionTests.swift in Sources */,
558571
0F7142C425514A6F00C18A61 /* ContentstackUtilsArrayTest.swift in Sources */,
572+
6749AC942F714E36007282C5 /* VariantUtilityTests.swift in Sources */,
559573
0FFD88EE266DE1A600BA5919 /* NodeParser.swift in Sources */,
560574
0FFD88F7266DE1FB00BA5919 /* JsonNodes.swift in Sources */,
561575
0F00785B26A5A0EB00FC4925 /* GQLJsonToHtml.swift in Sources */,

LICENSE

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
MIT License
22

3-
Copyright (c) 2016-2025 Contentstack
3+
Copyright (c) 2016-2026 Contentstack
44

55
Permission is hereby granted, free of charge, to any person obtaining a copy
66
of this software and associated documentation files (the "Software"), to deal

Sources/ContentstackUtils/ContentstackUtils.swift

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,10 @@ import Foundation
22

33
public struct ContentstackUtils {
44

5+
public enum VariantUtilityError: Error {
6+
case invalidArgument(String)
7+
}
8+
59
public struct GQL {
610
public static func jsonToHtml(rte document: [String: Any?], _ option: Option = Option()) throws -> Any {
711
do {
@@ -72,6 +76,79 @@ public struct ContentstackUtils {
7276
public static func jsonToHtml(node document: Node, _ option: Option = Option()) -> String {
7377
return nodeChildrenToHtml(children: document.children, option)
7478
}
79+
80+
public static func getVariantAliases(entry: [String: Any], contentTypeUid: String) throws -> [String: Any] {
81+
82+
try validateContentTypeUid(contentTypeUid)
83+
84+
guard let uid = entry["uid"] as? String, !uid.isEmpty else{
85+
throw VariantUtilityError.invalidArgument("entry uid is required.")
86+
}
87+
88+
guard let publish = entry["publish_details"] as? [String: Any],
89+
let variants = publish["variants"] as? [String: Any] else{
90+
return [
91+
"entry_uid": uid,
92+
"contenttype_uid": contentTypeUid,
93+
"variants": [] as [String]
94+
]
95+
}
96+
var aliases : [String] = []
97+
for(_, value) in variants {
98+
if let obj = value as? [String: Any],
99+
let alias = obj["alias"] as? String {
100+
aliases.append(alias)
101+
}
102+
}
103+
104+
return [
105+
"entry_uid": uid,
106+
"contenttype_uid": contentTypeUid,
107+
"variants": aliases
108+
]
109+
}
110+
111+
public static func getVariantAliases(entries: [[String: Any]], contentTypeUid: String) throws -> [[String: Any]] {
112+
try validateContentTypeUid(contentTypeUid)
113+
return try entries.map { entry in
114+
try getVariantAliases(entry: entry, contentTypeUid: contentTypeUid)
115+
}
116+
}
117+
118+
public static func getDataCsvariantsAttribute(entry: [String: Any]?, contentTypeUid: String) throws -> [String: Any]{
119+
guard let e = entry else {
120+
return ["data-csvariants": "[]"]
121+
}
122+
123+
let payload = try getVariantAliases(entry: e, contentTypeUid: contentTypeUid)
124+
let s = try jsonString(for: [payload])
125+
return ["data-csvariants": s]
126+
127+
}
128+
129+
public static func getDataCsvariantsAttribute(entries: [[String: Any]], contentTypeUid: String) throws -> [String: Any]{
130+
try validateContentTypeUid(contentTypeUid)
131+
let payloads = try getVariantAliases(entries: entries, contentTypeUid: contentTypeUid)
132+
let s = try jsonString(for: payloads)
133+
return ["data-csvariants": s]
134+
}
135+
136+
137+
private static func validateContentTypeUid(_ contentTypeUid: String) throws {
138+
if contentTypeUid.trimmingCharacters(in: .whitespacesAndNewlines).isEmpty {
139+
throw VariantUtilityError.invalidArgument("contentTypeUid must not be empty")
140+
}
141+
}
142+
143+
private static func jsonString(for array: [[String: Any]]) throws -> String{
144+
let data = try JSONSerialization.data(withJSONObject: array, options: [])
145+
guard let json = String(data: data, encoding: .utf8) else {
146+
throw VariantUtilityError.invalidArgument("Failed to encode JSON string")
147+
}
148+
return json
149+
}
150+
151+
75152

76153
static private func nodeChildrenToHtml(children nodes:[Node], _ option: Option) -> String {
77154
nodes.map{ (node) -> String in
@@ -156,3 +233,4 @@ public struct ContentstackUtils {
156233
return nil
157234
}
158235
}
236+

Tests/ContentstackUtilsTests/TestClient.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -26,4 +26,25 @@ class TestDecodable {
2626
static func getMultilevelEmbed() -> ContentBlock? {
2727
return decode("EntryEmbedded")
2828
}
29+
30+
/// Loads a JSON object from a file (e.g. `variantsEntries` → `variantsEntries.json`).
31+
/// Tries the test bundle first (Xcode), then the directory containing this source file (SwiftPM `swift test`).
32+
static func loadJSONObject(named fileName: String) throws -> [String: Any] {
33+
let url: URL
34+
if let path = Bundle(for: TestDecodable.self).path(forResource: fileName, ofType: "json") {
35+
url = URL(fileURLWithPath: path)
36+
} else {
37+
let testsDir = URL(fileURLWithPath: #filePath).deletingLastPathComponent()
38+
let fallback = testsDir.appendingPathComponent("\(fileName).json")
39+
guard FileManager.default.fileExists(atPath: fallback.path) else {
40+
throw NSError(domain: "TestDecodable", code: 1, userInfo: [NSLocalizedDescriptionKey: "Missing json: \(fileName).json"])
41+
}
42+
url = fallback
43+
}
44+
let data = try Data(contentsOf: url, options: .mappedIfSafe)
45+
guard let root = try JSONSerialization.jsonObject(with: data) as? [String: Any] else {
46+
throw NSError(domain: "TestDecodable", code: 2, userInfo: [NSLocalizedDescriptionKey: "Root is not a JSON object"])
47+
}
48+
return root
49+
}
2950
}

0 commit comments

Comments
 (0)