Skip to content

Commit 1f37850

Browse files
Merge pull request #181 from contentstack/staging
DX | 22-09-2025 | Release
2 parents 472dd01 + 4933253 commit 1f37850

File tree

9 files changed

+919
-2457
lines changed

9 files changed

+919
-2457
lines changed

.github/workflows/secrets-scan.yml

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
name: Secrets Scan
2+
on:
3+
pull_request:
4+
types: [opened, synchronize, reopened]
5+
jobs:
6+
security-secrets:
7+
runs-on: ubuntu-latest
8+
steps:
9+
- uses: actions/checkout@v4
10+
with:
11+
fetch-depth: '2'
12+
ref: '${{ github.event.pull_request.head.ref }}'
13+
- run: |
14+
git reset --soft HEAD~1
15+
- name: Install Talisman
16+
run: |
17+
# Download Talisman
18+
wget https://github.com/thoughtworks/talisman/releases/download/v1.37.0/talisman_linux_amd64 -O talisman
19+
20+
# Checksum verification
21+
checksum=$(sha256sum ./talisman | awk '{print $1}')
22+
if [ "$checksum" != "8e0ae8bb7b160bf10c4fa1448beb04a32a35e63505b3dddff74a092bccaaa7e4" ]; then exit 1; fi
23+
24+
# Make it executable
25+
chmod +x talisman
26+
- name: Run talisman
27+
run: |
28+
# Run Talisman with the pre-commit hook
29+
./talisman --githook pre-commit

.talismanrc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -3,8 +3,8 @@ fileignoreconfig:
33
ignore_detectors:
44
- filecontent
55
- filename: package-lock.json
6-
checksum: 030162c3c72502ccac30f3ce0172f1c2ae31a794544e0e1096771326780ea21b
6+
checksum: 88174adc8b9dcedecf549defe09988aa4ca95940801e923d829123ba2f3ef6f4
77
- filename: src/entry-editable.ts
8-
checksum: f9c4694229205fca252bb087482a3e408c6ad3b237cd108e337bcff49458db5c
8+
checksum: 3ba7af9ed1c1adef2e2bd5610099716562bebb8ba750d4b41ddda99fc9eaf115
99
- filename: .husky/pre-commit
1010
checksum: 5baabd7d2c391648163f9371f0e5e9484f8fb90fa2284cfc378732ec3192c193

CHANGELOG.md

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

3-
## [1.5.0](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.4.1) (2025-09-01)
3+
## [1.4.3](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.4.3) (2025-09-22)
4+
- Fix data-cslp generation logic in case of applied_variants
5+
6+
## [1.4.2](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.4.2) (2025-09-01)
47
- Improve null checks in find embedded entry and find embedded asset functions
58

69
## [1.4.1](https://github.com/contentstack/contentstack-utils-javascript/tree/v1.4.1) (2025-05-26)

CODEOWNERS

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1 @@
1-
* @contentstack/devex-pr-reviewers
2-
3-
.github/workflows/sca-scan.yml @contentstack/security-admin
4-
5-
.github/workflows/codeql-anaylsis.yml @contentstack/security-admin
6-
7-
**/.snyk @contentstack/security-admin
8-
9-
.github/workflows/policy-scan.yml @contentstack/security-admin
10-
11-
.github/workflows/issues-jira.yml @contentstack/security-admin
1+
* @contentstack/security-admin

__test__/entry-editable.test.ts

Lines changed: 144 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,5 @@
1-
import { EntryModel } from '../src'
21
import { addTags } from '../src/entry-editable'
3-
import { entry_global_field, entry_global_field_multiple, entry_modular_block, entry_reference, entry_with_text } from './mock/entry-editable-mock'
4-
import { entryMultipleContent } from './mock/entry-multiple-rich-text-content'
2+
import { entry_global_field, entry_global_field_multiple, entry_modular_block, entry_reference, entry_with_text, entry_with_applied_variants, entry_with_parent_path_variants } from './mock/entry-editable-mock'
53

64
describe('Entry editable test', () => {
75
it('Entry with text test', done => {
@@ -129,4 +127,147 @@ describe('Entry editable test', () => {
129127
done()
130128
})
131129

130+
// Tests for applied variants functionality
131+
describe('Applied Variants Tests', () => {
132+
it('Entry with applied variants should generate v2 tags with variant suffix', done => {
133+
addTags(entry_with_applied_variants, 'entry_asset', false)
134+
135+
// Field with direct variant match should get v2 prefix and variant suffix
136+
expect((entry_with_applied_variants as any)['$']['rich_text_editor']).toEqual('data-cslp=v2:entry_asset.entry_uid_1_variant_1.en-us.rich_text_editor')
137+
138+
// Nested field with direct variant match
139+
expect((entry_with_applied_variants as any)['nested']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_1_variant_2.en-us.nested.field')
140+
141+
// Field without variant should not have v2 prefix
142+
expect((entry_with_applied_variants as any)['nested']['$']['other_field']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.nested.other_field')
143+
expect((entry_with_applied_variants as any)['$']['rich_text_editor_multiple']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.rich_text_editor_multiple')
144+
145+
done()
146+
})
147+
148+
it('Entry with applied variants should return v2 objects when tagsAsObject is true', done => {
149+
addTags(entry_with_applied_variants, 'entry_asset', true)
150+
151+
// Field with direct variant match should get v2 prefix and variant suffix as object
152+
expect((entry_with_applied_variants as any)['$']['rich_text_editor']).toEqual({'data-cslp': 'v2:entry_asset.entry_uid_1_variant_1.en-us.rich_text_editor'})
153+
154+
// Nested field with direct variant match
155+
expect((entry_with_applied_variants as any)['nested']['$']['field']).toEqual({'data-cslp': 'v2:entry_asset.entry_uid_1_variant_2.en-us.nested.field'})
156+
157+
// Field without variant should not have v2 prefix
158+
expect((entry_with_applied_variants as any)['nested']['$']['other_field']).toEqual({'data-cslp': 'entry_asset.entry_uid_1.en-us.nested.other_field'})
159+
expect((entry_with_applied_variants as any)['$']['rich_text_editor_multiple']).toEqual({'data-cslp': 'entry_asset.entry_uid_1.en-us.rich_text_editor_multiple'})
160+
161+
done()
162+
})
163+
164+
it('Entry with parent path variants should find correct variant', done => {
165+
addTags(entry_with_parent_path_variants, 'entry_asset', false)
166+
167+
// Group field should get parent variant
168+
expect((entry_with_parent_path_variants as any)['$']['group']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_parent_variant.en-us.group')
169+
// Field under 'group' parent should get parent variant
170+
expect((entry_with_parent_path_variants as any)['group']['$']['other']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_parent_variant.en-us.group.other')
171+
// Field under 'group.nested' should get parent variant (group is longer match)
172+
expect((entry_with_parent_path_variants as any)['group']['nested']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_parent_variant.en-us.group.nested.field')
173+
// Field with exact deep path match should get deep variant
174+
expect((entry_with_parent_path_variants as any)['group']['nested']['deep']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_deep_variant.en-us.group.nested.deep.field')
175+
176+
// Field with the same starting path should not get parent variant
177+
expect((entry_with_parent_path_variants as any)['$']['group_multiple']).toEqual('data-cslp=entry_asset.entry_uid_3.en-us.group_multiple')
178+
179+
// Modular block content with variant should get v2 prefix and variant suffix
180+
expect((entry_with_parent_path_variants as any)['modular_blocks'][0]['$']['content']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_parent_variant.en-us.modular_blocks.0.content')
181+
// Modular block field inside a variantised parent should get v2 prefix and variant suffix
182+
expect((entry_with_parent_path_variants as any)['modular_blocks'][0]['content']['$']['title']).toEqual('data-cslp=v2:entry_asset.entry_uid_3_parent_variant.en-us.modular_blocks.0.content.title')
183+
184+
// Modular block content without variant should not have v2 prefix and variant suffix
185+
expect((entry_with_parent_path_variants as any)['modular_blocks'][1]['$']['content']).toEqual('data-cslp=entry_asset.entry_uid_3.en-us.modular_blocks.1.content')
186+
// Modular block field inside a non variantised parent should not get v2 prefix and variant suffix
187+
expect((entry_with_parent_path_variants as any)['modular_blocks'][1]['content']['$']['title']).toEqual('data-cslp=entry_asset.entry_uid_3.en-us.modular_blocks.1.content.title')
188+
189+
done()
190+
})
191+
192+
it('Entry with modular block variants should apply variants correctly', done => {
193+
addTags(entry_with_applied_variants, 'entry_asset', false)
194+
195+
// Modular block content with variant should get v2 prefix and variant suffix
196+
expect((entry_with_applied_variants as any)['modular_blocks'][1]['$']['content_from_variant']).toEqual('data-cslp=v2:entry_asset.entry_uid_1_variant_3.en-us.modular_blocks.1.content_from_variant')
197+
// Modular block field inside a variantised parent should get v2 prefix and variant suffix
198+
expect((entry_with_applied_variants as any)['modular_blocks'][1]['content_from_variant']['$']['title']).toEqual('data-cslp=v2:entry_asset.entry_uid_1_variant_3.en-us.modular_blocks.1.content_from_variant.title')
199+
// Field inside a variantised parent with a different variant should get v2 prefix and variant suffix of that variant
200+
expect((entry_with_applied_variants as any)['modular_blocks'][1]['content_from_variant']['$']['different_from_parent_variant']).toEqual('data-cslp=v2:entry_asset.entry_uid_1_variant_4.en-us.modular_blocks.1.content_from_variant.different_from_parent_variant')
201+
202+
// Modular block content without variant should get v2 prefix and variant suffix
203+
expect((entry_with_applied_variants as any)['modular_blocks'][0]['$']['content']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.modular_blocks.0.content')
204+
// Modular block field without variant should not have v2 prefix and variant suffix
205+
expect((entry_with_applied_variants as any)['modular_blocks'][0]['content']['$']['title']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.modular_blocks.0.content.title')
206+
207+
done()
208+
})
209+
210+
it('Entry without applied variants should work normally', done => {
211+
addTags(entry_with_text, 'entry_asset', false)
212+
213+
// Should not have v2 prefix when no variants are applied
214+
expect((entry_with_text as any)['$']['rich_text_editor']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.rich_text_editor')
215+
expect((entry_with_text as any)['$']['rich_text_editor_multiple']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.rich_text_editor_multiple')
216+
217+
done()
218+
})
219+
220+
it('Entry with empty applied variants should work normally', done => {
221+
const entryWithEmptyVariants = {
222+
...entry_with_text,
223+
_applied_variants: {}
224+
}
225+
226+
addTags(entryWithEmptyVariants, 'entry_asset', false)
227+
228+
// Should not have v2 prefix when variants object is empty
229+
expect((entryWithEmptyVariants as any)['$']['rich_text_editor']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.rich_text_editor')
230+
expect((entryWithEmptyVariants as any)['$']['rich_text_editor_multiple']).toEqual('data-cslp=entry_asset.entry_uid_1.en-us.rich_text_editor_multiple')
231+
232+
done()
233+
})
234+
235+
it('Variant path sorting should work correctly for nested paths', done => {
236+
const entryWithComplexVariants = {
237+
"_version": 10,
238+
"locale": "en-us",
239+
"uid": "entry_uid_test",
240+
"ACL": {},
241+
"_applied_variants": {
242+
"a": "variant_a",
243+
"a.b": "variant_ab",
244+
"a.b.c": "variant_abc",
245+
"a.b.c.d": "variant_abcd"
246+
},
247+
"a": {
248+
"b": {
249+
"c": {
250+
"d": {
251+
"field": "deep field"
252+
},
253+
"field": "c field"
254+
},
255+
"field": "b field"
256+
},
257+
"field": "a field"
258+
}
259+
}
260+
261+
addTags(entryWithComplexVariants, 'entry_asset', false)
262+
263+
// Should use the longest matching path variant
264+
expect((entryWithComplexVariants as any)['a']['b']['c']['d']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_test_variant_abcd.en-us.a.b.c.d.field')
265+
expect((entryWithComplexVariants as any)['a']['b']['c']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_test_variant_abc.en-us.a.b.c.field')
266+
expect((entryWithComplexVariants as any)['a']['b']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_test_variant_ab.en-us.a.b.field')
267+
expect((entryWithComplexVariants as any)['a']['$']['field']).toEqual('data-cslp=v2:entry_asset.entry_uid_test_variant_a.en-us.a.field')
268+
269+
done()
270+
})
271+
})
272+
132273
})

__test__/mock/entry-editable-mock.ts

Lines changed: 103 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ const entry_modular_block = {
2222
"<p>module 2&nbsp;</p><figure class=\"embedded-asset\" data-redactor-type=\"embed\" data-widget-code=\"\" data-sys-asset-filelink=\"https://image.url/11.jpg\" data-sys-asset-uid=\"entry_uid_34\" data-sys-asset-filename=\"11.jpg\" data-sys-asset-contenttype=\"image/jpeg\" type=\"asset\" sys-style-type=\"display\"></figure>"
2323
],
2424
"_metadata": {
25-
"uid": "metadata_uid"
25+
"uid": "metadata_uid_1"
2626
}
27-
}
27+
},
2828
},
2929
{
3030
"global_modular": {
@@ -46,13 +46,13 @@ const entry_modular_block = {
4646
"<p>Module 2</p><div class=\"redactor-component embedded-entry block-entry redactor-component-active\" data-redactor-type=\"embed\" data-widget-code=\"\" data-sys-entry-uid=\"entry_uid_10\" data-sys-entry-locale=\"en-us\" data-sys-content-type-uid=\"0_solve\" data-sys-can-edit=\"true\" sys-style-type=\"block\" type=\"entry\"></div>"
4747
],
4848
"_metadata": {
49-
"uid": "metadata_uid"
49+
"uid": "metadata_uid_1"
5050
}
51-
}
51+
},
5252
}
5353
],
5454
"_metadata": {
55-
"uid": "metadata_uid"
55+
"uid": "metadata_uid_2"
5656
}
5757
}
5858
}
@@ -106,9 +106,9 @@ const entry_global_field = {
106106
"<p>global modular 2<span class=\"redactor-component embedded-entry inline-entry\" data-redactor-type=\"embed\" data-widget-code=\"\" data-sys-entry-uid=\"entry_uid_12\" data-sys-entry-locale=\"en-us\" data-sys-content-type-uid=\"0_bug_1\" data-sys-can-edit=\"true\" sys-style-type=\"inline\" type=\"entry\"></span></p>"
107107
],
108108
"_metadata": {
109-
"uid": "metadata_uid"
109+
"uid": "metadata_uid_1"
110110
}
111-
}
111+
},
112112
}
113113
]
114114
},
@@ -139,21 +139,113 @@ const entry_global_field_multiple = {
139139
"<p>Global multiple modular</p><figure class=\"embedded-asset\" data-redactor-type=\"embed\" data-widget-code=\"\" data-sys-asset-filelink=\"https://image.url/11.jpg\" data-sys-asset-uid=\"entry_uid_34\" data-sys-asset-filename=\"11.jpg\" data-sys-asset-contenttype=\"image/jpeg\" type=\"asset\" sys-style-type=\"display\"></figure>\n<p> 2</p>"
140140
],
141141
"_metadata": {
142-
"uid": "metadata_uid"
142+
"uid": "metadata_uid_1"
143143
}
144-
}
144+
},
145145
}
146146
],
147147
"_metadata": {
148-
"uid": "metadata_uid"
148+
"uid": "metadata_uid_1"
149+
}
150+
}
151+
]
152+
}
153+
// Mock entry with applied variants for testing variant functionality
154+
const entry_with_applied_variants = {
155+
"_version": 10,
156+
"locale": "en-us",
157+
"uid": "entry_uid_1",
158+
"ACL": {},
159+
"_applied_variants": {
160+
"rich_text_editor": "variant_1",
161+
"nested.field": "variant_2",
162+
"modular_blocks.content_from_variant.metadata_uid_2": "variant_3",
163+
"modular_blocks.content_from_variant.metadata_uid_2.different_from_parent_variant": "variant_4"
164+
},
165+
"rich_text_editor": "<p>Content with variant</p>",
166+
"rich_text_editor_multiple": [
167+
"<p>Multiple content with variant</p>"
168+
],
169+
"nested": {
170+
"field": "nested field content",
171+
"other_field": "other nested content"
172+
},
173+
"modular_blocks": [
174+
{
175+
"content": {
176+
"title": "modular title",
177+
"_metadata": {
178+
"uid": "metadata_uid_1"
179+
}
180+
}
181+
},
182+
{
183+
"content_from_variant": {
184+
"title": "modular title from variant",
185+
"different_from_parent_variant": "different from parent variant",
186+
"_metadata": {
187+
"uid": "metadata_uid_2"
188+
}
189+
}
190+
}
191+
]
192+
}
193+
194+
195+
// Mock entry with nested parent path variants
196+
const entry_with_parent_path_variants = {
197+
"_version": 10,
198+
"locale": "en-us",
199+
"uid": "entry_uid_3",
200+
"ACL": {},
201+
"_applied_variants": {
202+
"group": "parent_variant",
203+
"group.nested.deep": "deep_variant",
204+
"modular_blocks.content.metadata_uid_1": "parent_variant"
205+
},
206+
"group": {
207+
"nested": {
208+
"field": "nested field",
209+
"deep": {
210+
"field": "deep field"
211+
}
212+
},
213+
"other": "other field"
214+
},
215+
"modular_blocks": [
216+
{
217+
"content": {
218+
"title": "modular title",
219+
"_metadata": {
220+
"uid": "metadata_uid_1"
221+
}
222+
},
223+
},
224+
{
225+
"content": {
226+
"title": "modular title 2",
227+
"_metadata": {
228+
"uid": "metadata_uid_2"
229+
}
230+
},
231+
}
232+
],
233+
"group_multiple": [
234+
{
235+
"other": "other field",
236+
"_metadata": {
237+
"uid": "metadata_uid_1"
149238
}
150239
}
151240
]
152241
}
242+
153243
export {
154244
entry_with_text,
155245
entry_reference,
156246
entry_global_field,
157247
entry_modular_block,
158-
entry_global_field_multiple
248+
entry_global_field_multiple,
249+
entry_with_applied_variants,
250+
entry_with_parent_path_variants
159251
}

0 commit comments

Comments
 (0)