strapi-plugin-bulk-updater 1.3.8 → 1.3.10

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (29) hide show
  1. package/dist/_chunks/{BulkUpdater-BosdSbGF.js → BulkUpdater-DRHQBZZV.js} +20 -4
  2. package/dist/_chunks/BulkUpdater-DRHQBZZV.js.map +1 -0
  3. package/dist/_chunks/{BulkUpdater-DUrmamj-.mjs → BulkUpdater-J21ZgJWx.mjs} +20 -4
  4. package/dist/_chunks/BulkUpdater-J21ZgJWx.mjs.map +1 -0
  5. package/dist/_chunks/{ScopedQuotaSelectInput-CyGlYa9x.js → ScopedQuotaSelectInput-BsBpmwun.js} +79 -11
  6. package/dist/_chunks/ScopedQuotaSelectInput-BsBpmwun.js.map +1 -0
  7. package/dist/_chunks/{ScopedQuotaSelectInput-Cy3eEj9-.mjs → ScopedQuotaSelectInput-YKdQgrPt.mjs} +79 -11
  8. package/dist/_chunks/ScopedQuotaSelectInput-YKdQgrPt.mjs.map +1 -0
  9. package/dist/_chunks/{SettingsPage-Bdse5MP4.mjs → SettingsPage-BDObdCIY.mjs} +2 -2
  10. package/dist/_chunks/{SettingsPage-Bdse5MP4.mjs.map → SettingsPage-BDObdCIY.mjs.map} +1 -1
  11. package/dist/_chunks/{SettingsPage-B6Ia7SVS.js → SettingsPage-M-HQ8Jtr.js} +2 -2
  12. package/dist/_chunks/{SettingsPage-B6Ia7SVS.js.map → SettingsPage-M-HQ8Jtr.js.map} +1 -1
  13. package/dist/_chunks/{ValidationRulesTab-BqGz1VLo.js → ValidationRulesTab-DuvL-K4F.js} +21 -8
  14. package/dist/_chunks/ValidationRulesTab-DuvL-K4F.js.map +1 -0
  15. package/dist/_chunks/{ValidationRulesTab-Dqkson67.mjs → ValidationRulesTab-VeRr-vxa.mjs} +21 -8
  16. package/dist/_chunks/ValidationRulesTab-VeRr-vxa.mjs.map +1 -0
  17. package/dist/admin/index.js +3 -3
  18. package/dist/admin/index.mjs +3 -3
  19. package/dist/server/index.js +223 -42
  20. package/dist/server/index.js.map +1 -1
  21. package/dist/server/index.mjs +223 -42
  22. package/dist/server/index.mjs.map +1 -1
  23. package/package.json +1 -1
  24. package/dist/_chunks/BulkUpdater-BosdSbGF.js.map +0 -1
  25. package/dist/_chunks/BulkUpdater-DUrmamj-.mjs.map +0 -1
  26. package/dist/_chunks/ScopedQuotaSelectInput-Cy3eEj9-.mjs.map +0 -1
  27. package/dist/_chunks/ScopedQuotaSelectInput-CyGlYa9x.js.map +0 -1
  28. package/dist/_chunks/ValidationRulesTab-BqGz1VLo.js.map +0 -1
  29. package/dist/_chunks/ValidationRulesTab-Dqkson67.mjs.map +0 -1
@@ -842,7 +842,9 @@ function buildDeepPopulate$1(attributes, allComponents, depth2 = 0) {
842
842
  populate2[key] = true;
843
843
  }
844
844
  } else if (def.type === "dynamiczone") {
845
- populate2[key] = true;
845
+ populate2[key] = {
846
+ populate: "*"
847
+ };
846
848
  } else if (def.type === "relation" || def.type === "media") {
847
849
  populate2[key] = true;
848
850
  }
@@ -856,10 +858,28 @@ function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, max
856
858
  const discoveredAttrs = [];
857
859
  for (const [subKey, subAttr] of Object.entries(compSchema.attributes)) {
858
860
  if (systemFields.includes(subKey)) continue;
859
- if (subAttr.type === "dynamiczone") continue;
860
861
  const fullPath = prefix ? `${prefix}.${subKey}` : subKey;
862
+ if (subAttr.type === "dynamiczone") {
863
+ const dzComps = Array.isArray(subAttr.components) ? subAttr.components : [];
864
+ for (const dzCompUid of dzComps) {
865
+ const deeper = discoverComponentAttributes(
866
+ strapi2,
867
+ dzCompUid,
868
+ fullPath,
869
+ currentDepth + 1,
870
+ maxDepth,
871
+ systemFields,
872
+ allContentTypes,
873
+ relationsByAttr,
874
+ allRelationsMeta,
875
+ attributesMeta
876
+ );
877
+ discoveredAttrs.push(...deeper);
878
+ }
879
+ continue;
880
+ }
861
881
  if (subAttr.type === "component") {
862
- if (!subAttr.repeatable && subAttr.component) {
882
+ if (subAttr.component) {
863
883
  const deeper = discoverComponentAttributes(
864
884
  strapi2,
865
885
  subAttr.component,
@@ -901,46 +921,110 @@ function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, max
901
921
  function isDeepComponentField(contentType, allComponents, attribute) {
902
922
  if (!attribute || !attribute.includes(".")) return false;
903
923
  const parts = attribute.split(".");
904
- let currentSchema = contentType;
905
- for (let i = 0; i < parts.length - 1; i++) {
906
- const part = parts[i];
907
- const attrDef = currentSchema?.attributes?.[part];
908
- if (!attrDef || attrDef.type !== "component" || !attrDef.component) return false;
909
- currentSchema = allComponents[attrDef.component];
910
- if (!currentSchema) return false;
924
+ function checkPath(currentSchema, pathParts) {
925
+ if (pathParts.length === 0) return true;
926
+ if (!currentSchema || !currentSchema.attributes) return false;
927
+ const [head, ...tail] = pathParts;
928
+ const attrDef = currentSchema.attributes[head];
929
+ if (!attrDef) return false;
930
+ if (tail.length === 0) {
931
+ return true;
932
+ }
933
+ if (attrDef.type === "component" && attrDef.component) {
934
+ const comp = allComponents[attrDef.component];
935
+ return checkPath(comp, tail);
936
+ }
937
+ if (attrDef.type === "dynamiczone") {
938
+ const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
939
+ return compUids.some((compUid) => {
940
+ const comp = allComponents[compUid];
941
+ return checkPath(comp, tail);
942
+ });
943
+ }
944
+ return false;
911
945
  }
912
- const leafField = parts[parts.length - 1];
913
- return Boolean(currentSchema?.attributes?.[leafField]);
946
+ return checkPath(contentType, parts);
914
947
  }
915
948
  function getLeafAttributeSchema(contentType, allComponents, attribute) {
916
949
  if (!attribute) return null;
917
950
  if (!attribute.includes(".")) return contentType?.attributes?.[attribute] || null;
918
951
  const parts = attribute.split(".");
919
- let currentSchema = contentType;
920
- for (let i = 0; i < parts.length - 1; i++) {
921
- const part = parts[i];
922
- const attrDef = currentSchema?.attributes?.[part];
923
- if (!attrDef || attrDef.type !== "component" || !attrDef.component) return null;
924
- currentSchema = allComponents[attrDef.component];
925
- if (!currentSchema) return null;
952
+ function resolveLeaf(currentSchema, pathParts) {
953
+ if (!currentSchema || !currentSchema.attributes) return null;
954
+ const [head, ...tail] = pathParts;
955
+ const attrDef = currentSchema.attributes[head];
956
+ if (!attrDef) return null;
957
+ if (tail.length === 0) {
958
+ return attrDef;
959
+ }
960
+ if (attrDef.type === "component" && attrDef.component) {
961
+ return resolveLeaf(allComponents[attrDef.component], tail);
962
+ }
963
+ if (attrDef.type === "dynamiczone") {
964
+ const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
965
+ for (const compUid of compUids) {
966
+ const found = resolveLeaf(allComponents[compUid], tail);
967
+ if (found) return found;
968
+ }
969
+ }
970
+ return null;
926
971
  }
927
- return currentSchema?.attributes?.[parts[parts.length - 1]] || null;
972
+ return resolveLeaf(contentType, parts);
928
973
  }
929
974
  function setDeepProperty(obj, pathParts, value) {
930
975
  if (pathParts.length === 0) return value;
931
- const current = { ...obj || {} };
932
976
  const [head, ...tail] = pathParts;
977
+ if (Array.isArray(obj)) {
978
+ if (/^\d+$/.test(head)) {
979
+ const idx = parseInt(head, 10);
980
+ const copy = [...obj];
981
+ copy[idx] = setDeepProperty(copy[idx] || {}, tail, value);
982
+ return copy;
983
+ }
984
+ return obj.map((item) => {
985
+ if (!item || typeof item !== "object") return item;
986
+ if (head in item || tail.length > 0 && typeof item[head] === "object") {
987
+ return {
988
+ ...item,
989
+ [head]: setDeepProperty(item[head], tail, value)
990
+ };
991
+ }
992
+ return item;
993
+ });
994
+ }
995
+ const current = { ...obj || {} };
933
996
  if (tail.length === 0) {
934
997
  current[head] = value;
935
998
  } else {
936
- current[head] = setDeepProperty(current[head] || {}, tail, value);
999
+ const subTarget = current[head];
1000
+ if (Array.isArray(subTarget)) {
1001
+ current[head] = setDeepProperty(subTarget, tail, value);
1002
+ } else {
1003
+ current[head] = setDeepProperty(subTarget || {}, tail, value);
1004
+ }
937
1005
  }
938
1006
  return current;
939
1007
  }
940
1008
  function getDeepProperty(obj, path2) {
941
1009
  if (!obj || !path2) return void 0;
942
- if (!path2.includes(".")) return obj[path2];
943
- return path2.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
1010
+ const parts = path2.split(".");
1011
+ function extract(current, remainingParts) {
1012
+ if (current === void 0 || current === null) return void 0;
1013
+ if (remainingParts.length === 0) return current;
1014
+ const [head, ...tail] = remainingParts;
1015
+ if (Array.isArray(current)) {
1016
+ if (/^\d+$/.test(head)) {
1017
+ return extract(current[parseInt(head, 10)], tail);
1018
+ }
1019
+ for (const item of current) {
1020
+ const found = extract(item?.[head], tail);
1021
+ if (found !== void 0 && found !== null) return found;
1022
+ }
1023
+ return void 0;
1024
+ }
1025
+ return extract(current[head], tail);
1026
+ }
1027
+ return extract(obj, parts);
944
1028
  }
945
1029
  function buildDeepFilter(pathParts, condition) {
946
1030
  if (pathParts.length === 0) return condition;
@@ -1170,7 +1254,8 @@ async function executeBatchInternal(strapi2, {
1170
1254
  findOneArgs.populate = populateOption;
1171
1255
  }
1172
1256
  const currentEntryWithPopulate = await strapi2.documents(target.uid).findOne(findOneArgs);
1173
- const existingRoot = currentEntryWithPopulate?.[rootKey] || {};
1257
+ const isRootDz = contentType?.attributes?.[rootKey]?.type === "dynamiczone";
1258
+ const existingRoot = currentEntryWithPopulate?.[rootKey] || (isRootDz ? [] : {});
1174
1259
  const currentVal2 = getDeepProperty(existingRoot, parts.slice(1).join("."));
1175
1260
  let newValue2 = value;
1176
1261
  if (regexObj && typeof currentVal2 === "string") {
@@ -1650,10 +1735,29 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
1650
1735
  const nestedComponentAttrs = [];
1651
1736
  const validAttrs = Object.keys(rawAttrs).filter((attrKey) => {
1652
1737
  if (systemFields.includes(attrKey)) return false;
1653
- if (rawAttrs[attrKey].type === "dynamiczone") return false;
1738
+ if (rawAttrs[attrKey].type === "dynamiczone") {
1739
+ const dzDef = rawAttrs[attrKey];
1740
+ const dzComponents = Array.isArray(dzDef.components) ? dzDef.components : [];
1741
+ for (const compUid of dzComponents) {
1742
+ const compAttrs = discoverComponentAttributes(
1743
+ strapi2,
1744
+ compUid,
1745
+ attrKey,
1746
+ 1,
1747
+ 5,
1748
+ systemFields,
1749
+ allContentTypes,
1750
+ relationsByAttr,
1751
+ allRelationsMeta,
1752
+ colAttributesMeta
1753
+ );
1754
+ nestedComponentAttrs.push(...compAttrs);
1755
+ }
1756
+ return false;
1757
+ }
1654
1758
  if (rawAttrs[attrKey].type === "component") {
1655
1759
  const compDef = rawAttrs[attrKey];
1656
- if (!compDef.repeatable && compDef.component) {
1760
+ if (compDef.component) {
1657
1761
  const compAttrs = discoverComponentAttributes(
1658
1762
  strapi2,
1659
1763
  compDef.component,
@@ -3617,15 +3721,70 @@ function extractHeadingsFromRichText(content) {
3617
3721
  }
3618
3722
  return counts;
3619
3723
  }
3724
+ function cleanKey(str2) {
3725
+ return (str2 || "").replace(/[-_]/g, "").toLowerCase().trim();
3726
+ }
3620
3727
  function matchesPath(normalizedPath, leafKey, targetField) {
3621
3728
  if (!targetField) return false;
3622
- const pLower = (normalizedPath || "").toLowerCase().trim();
3623
- const kLower = (leafKey || "").toLowerCase().trim();
3624
- const tLower = targetField.toLowerCase().trim();
3625
- if (!tLower.includes(".")) {
3626
- return kLower === tLower || pLower === tLower || pLower.endsWith(`.${tLower}`);
3729
+ const pClean = cleanKey(normalizedPath);
3730
+ const kClean = cleanKey(leafKey);
3731
+ const tClean = cleanKey(targetField);
3732
+ if (kClean === tClean || pClean === tClean) return true;
3733
+ const targetSegments = targetField.replace(/^api::/i, "").split(".").map(cleanKey).filter(Boolean);
3734
+ const pathSegments = normalizedPath.split(".").map(cleanKey).filter(Boolean);
3735
+ if (targetSegments.length === 0 || pathSegments.length === 0) return false;
3736
+ if (targetSegments.length === 1) {
3737
+ const single = targetSegments[0];
3738
+ return kClean === single || pathSegments[pathSegments.length - 1] === single;
3739
+ }
3740
+ let isSuffix = true;
3741
+ for (let i = 0; i < targetSegments.length; i++) {
3742
+ const tSeg = targetSegments[targetSegments.length - 1 - i];
3743
+ const pSeg = pathSegments[pathSegments.length - 1 - i];
3744
+ if (tSeg !== "*" && tSeg !== pSeg) {
3745
+ isSuffix = false;
3746
+ break;
3747
+ }
3627
3748
  }
3628
- return pLower === tLower || pLower.endsWith(`.${tLower}`) || pLower.endsWith(tLower);
3749
+ if (isSuffix) return true;
3750
+ for (let startIdx = 1; startIdx < targetSegments.length; startIdx++) {
3751
+ const subTarget = targetSegments.slice(startIdx);
3752
+ if (subTarget.length > pathSegments.length) continue;
3753
+ let subSuffix = true;
3754
+ for (let i = 0; i < subTarget.length; i++) {
3755
+ const tSeg = subTarget[subTarget.length - 1 - i];
3756
+ const pSeg = pathSegments[pathSegments.length - 1 - i];
3757
+ if (tSeg !== "*" && tSeg !== pSeg) {
3758
+ subSuffix = false;
3759
+ break;
3760
+ }
3761
+ }
3762
+ if (subSuffix) return true;
3763
+ }
3764
+ let pIdx = 0;
3765
+ let matchesInOrder = true;
3766
+ let tStart = 0;
3767
+ if (targetSegments.length > 2 && targetSegments[0] !== pathSegments[0] && targetSegments[0] !== "*") {
3768
+ tStart = 1;
3769
+ }
3770
+ for (let tIdx = tStart; tIdx < targetSegments.length; tIdx++) {
3771
+ const tSeg = targetSegments[tIdx];
3772
+ if (tSeg === "*") continue;
3773
+ let found = false;
3774
+ while (pIdx < pathSegments.length) {
3775
+ if (pathSegments[pIdx] === tSeg) {
3776
+ found = true;
3777
+ pIdx++;
3778
+ break;
3779
+ }
3780
+ pIdx++;
3781
+ }
3782
+ if (!found) {
3783
+ matchesInOrder = false;
3784
+ break;
3785
+ }
3786
+ }
3787
+ return matchesInOrder;
3629
3788
  }
3630
3789
  const validationRulesService = ({ strapi: strapi2 }) => ({
3631
3790
  async getStore() {
@@ -3739,10 +3898,13 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3739
3898
  function componentHasField(compUid, targetField2) {
3740
3899
  const comp = allComponents[compUid];
3741
3900
  if (!comp?.attributes) return false;
3901
+ if (matchesPath(compUid, compUid, targetField2)) return true;
3742
3902
  for (const [attrName, attrDef] of Object.entries(comp.attributes)) {
3743
- if (matchesPath(attrName, attrName, targetField2)) return true;
3903
+ if (matchesPath(attrName, attrName, targetField2) || matchesPath(`${compUid}.${attrName}`, attrName, targetField2)) return true;
3744
3904
  if (attrDef.type === "component" && attrDef.component) {
3745
3905
  if (componentHasField(attrDef.component, targetField2)) return true;
3906
+ } else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
3907
+ if (attrDef.components.some((c) => componentHasField(c, targetField2))) return true;
3746
3908
  }
3747
3909
  }
3748
3910
  return false;
@@ -3759,15 +3921,15 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3759
3921
  if (attrDef.type === "component" && attrDef.component) {
3760
3922
  if (componentHasField(attrDef.component, targetField)) {
3761
3923
  hasField = true;
3762
- populateAttrs[attrKey] = true;
3924
+ populateAttrs[attrKey] = { populate: "*" };
3763
3925
  }
3764
3926
  } else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
3765
3927
  const matchingComp = attrDef.components.some(
3766
- (compUid) => componentHasField(compUid, targetField)
3928
+ (compUid) => componentHasField(compUid, targetField) || matchesPath(`${attrKey}.${compUid}`, compUid, targetField)
3767
3929
  );
3768
3930
  if (matchingComp) {
3769
3931
  hasField = true;
3770
- populateAttrs[attrKey] = true;
3932
+ populateAttrs[attrKey] = { populate: "*" };
3771
3933
  }
3772
3934
  }
3773
3935
  }
@@ -3775,7 +3937,11 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3775
3937
  }
3776
3938
  const candidateUids = Object.keys(allContentTypes).filter((uid) => {
3777
3939
  if (!uid.startsWith("api::") || uid.includes("bulk-updater")) return false;
3778
- if (rule.collectionUid && rule.collectionUid !== "*" && rule.collectionUid !== uid) {
3940
+ if (rule.collectionUid && rule.collectionUid !== "*") {
3941
+ if (rule.collectionUid === uid) return true;
3942
+ const cleanRuleCol = cleanKey(rule.collectionUid);
3943
+ const cleanUid = cleanKey(uid);
3944
+ if (cleanUid.includes(cleanRuleCol) || cleanRuleCol.includes(cleanUid)) return true;
3779
3945
  return false;
3780
3946
  }
3781
3947
  return true;
@@ -3785,7 +3951,8 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3785
3951
  const ct = allContentTypes[uid];
3786
3952
  if (!ct) continue;
3787
3953
  const { hasField, populateAttrs } = schemaHasField(ct);
3788
- if (hasField || rule.collectionUid && rule.collectionUid === uid) {
3954
+ const isTargetCol = rule.collectionUid && (rule.collectionUid === uid || cleanKey(uid).includes(cleanKey(rule.collectionUid)));
3955
+ if (hasField || isTargetCol) {
3789
3956
  relevantCollections.push({
3790
3957
  uid,
3791
3958
  displayName: ct.info?.displayName || uid,
@@ -3808,11 +3975,15 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3808
3975
  for (const entry of entries) {
3809
3976
  let traverse = function(obj, currentPath = "") {
3810
3977
  if (!obj || typeof obj !== "object") return;
3978
+ const compName = typeof obj.__component === "string" ? obj.__component : "";
3811
3979
  for (const key of Object.keys(obj)) {
3980
+ if (key === "__component") continue;
3812
3981
  const val = obj[key];
3813
3982
  const newPath = currentPath ? `${currentPath}.${key}` : key;
3814
3983
  const normalizedPath = newPath.replace(/\.\d+/g, "");
3815
- if (matchesPath(normalizedPath, key, targetField) && typeof val === "string" && val.trim()) {
3984
+ const compPath = compName ? (currentPath ? `${currentPath}.${compName}.${key}` : `${compName}.${key}`).replace(/\.\d+/g, "") : "";
3985
+ const isMatch = matchesPath(normalizedPath, key, targetField) || (compPath ? matchesPath(compPath, key, targetField) : false);
3986
+ if (isMatch && typeof val === "string" && val.trim()) {
3816
3987
  const lower = val.toLowerCase().trim();
3817
3988
  structuredCounts[lower] = (structuredCounts[lower] || 0) + 1;
3818
3989
  }
@@ -34145,12 +34316,16 @@ const index = {
34145
34316
  if (data && typeof data === "object") {
34146
34317
  let scanDataTree = function(obj, currentPath = "") {
34147
34318
  if (!obj || typeof obj !== "object") return;
34319
+ const compName = typeof obj.__component === "string" ? obj.__component : "";
34148
34320
  for (const key of Object.keys(obj)) {
34321
+ if (key === "__component") continue;
34149
34322
  const val = obj[key];
34150
34323
  const newPath = currentPath ? `${currentPath}.${key}` : key;
34151
34324
  const normalizedPath = newPath.replace(/\.\d+/g, "");
34325
+ const compPath = compName ? (currentPath ? `${currentPath}.${compName}.${key}` : `${compName}.${key}`).replace(/\.\d+/g, "") : "";
34152
34326
  for (const rule of effectiveRules) {
34153
- if (matchesPath(normalizedPath, key, rule.targetField) && typeof val === "string" && val.trim()) {
34327
+ const isMatch = matchesPath(normalizedPath, key, rule.targetField) || (compPath ? matchesPath(compPath, key, rule.targetField) : false);
34328
+ if (isMatch && typeof val === "string" && val.trim()) {
34154
34329
  const lowerVal = val.toLowerCase().trim();
34155
34330
  structuredCounts[rule.id] = structuredCounts[rule.id] || {};
34156
34331
  structuredCounts[rule.id][lowerVal] = (structuredCounts[rule.id][lowerVal] || 0) + 1;
@@ -34188,7 +34363,13 @@ const index = {
34188
34363
  strapi2.log.warn("[bulk-updater] Failed to fetch active UI validation rules:", err);
34189
34364
  }
34190
34365
  const effectiveRules = [
34191
- ...activeUiRules.filter((r) => !r.collectionUid || r.collectionUid === "*" || r.collectionUid === context.uid).map((r) => ({
34366
+ ...activeUiRules.filter((r) => {
34367
+ if (!r.collectionUid || r.collectionUid === "*") return true;
34368
+ if (r.collectionUid === context.uid) return true;
34369
+ const cleanRuleCol = cleanKey(r.collectionUid);
34370
+ const cleanCtxCol = cleanKey(context.uid);
34371
+ return cleanCtxCol.includes(cleanRuleCol) || cleanRuleCol.includes(cleanCtxCol);
34372
+ }).map((r) => ({
34192
34373
  id: r.id || r.targetField,
34193
34374
  name: r.name || r.targetField,
34194
34375
  collectionUid: r.collectionUid || "*",