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
@@ -855,7 +855,9 @@ function buildDeepPopulate$1(attributes, allComponents, depth2 = 0) {
855
855
  populate2[key] = true;
856
856
  }
857
857
  } else if (def.type === "dynamiczone") {
858
- populate2[key] = true;
858
+ populate2[key] = {
859
+ populate: "*"
860
+ };
859
861
  } else if (def.type === "relation" || def.type === "media") {
860
862
  populate2[key] = true;
861
863
  }
@@ -869,10 +871,28 @@ function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, max
869
871
  const discoveredAttrs = [];
870
872
  for (const [subKey, subAttr] of Object.entries(compSchema.attributes)) {
871
873
  if (systemFields.includes(subKey)) continue;
872
- if (subAttr.type === "dynamiczone") continue;
873
874
  const fullPath = prefix ? `${prefix}.${subKey}` : subKey;
875
+ if (subAttr.type === "dynamiczone") {
876
+ const dzComps = Array.isArray(subAttr.components) ? subAttr.components : [];
877
+ for (const dzCompUid of dzComps) {
878
+ const deeper = discoverComponentAttributes(
879
+ strapi2,
880
+ dzCompUid,
881
+ fullPath,
882
+ currentDepth + 1,
883
+ maxDepth,
884
+ systemFields,
885
+ allContentTypes,
886
+ relationsByAttr,
887
+ allRelationsMeta,
888
+ attributesMeta
889
+ );
890
+ discoveredAttrs.push(...deeper);
891
+ }
892
+ continue;
893
+ }
874
894
  if (subAttr.type === "component") {
875
- if (!subAttr.repeatable && subAttr.component) {
895
+ if (subAttr.component) {
876
896
  const deeper = discoverComponentAttributes(
877
897
  strapi2,
878
898
  subAttr.component,
@@ -914,46 +934,110 @@ function discoverComponentAttributes(strapi2, compUid, prefix, currentDepth, max
914
934
  function isDeepComponentField(contentType, allComponents, attribute) {
915
935
  if (!attribute || !attribute.includes(".")) return false;
916
936
  const parts = attribute.split(".");
917
- let currentSchema = contentType;
918
- for (let i = 0; i < parts.length - 1; i++) {
919
- const part = parts[i];
920
- const attrDef = currentSchema?.attributes?.[part];
921
- if (!attrDef || attrDef.type !== "component" || !attrDef.component) return false;
922
- currentSchema = allComponents[attrDef.component];
923
- if (!currentSchema) return false;
937
+ function checkPath(currentSchema, pathParts) {
938
+ if (pathParts.length === 0) return true;
939
+ if (!currentSchema || !currentSchema.attributes) return false;
940
+ const [head, ...tail] = pathParts;
941
+ const attrDef = currentSchema.attributes[head];
942
+ if (!attrDef) return false;
943
+ if (tail.length === 0) {
944
+ return true;
945
+ }
946
+ if (attrDef.type === "component" && attrDef.component) {
947
+ const comp = allComponents[attrDef.component];
948
+ return checkPath(comp, tail);
949
+ }
950
+ if (attrDef.type === "dynamiczone") {
951
+ const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
952
+ return compUids.some((compUid) => {
953
+ const comp = allComponents[compUid];
954
+ return checkPath(comp, tail);
955
+ });
956
+ }
957
+ return false;
924
958
  }
925
- const leafField = parts[parts.length - 1];
926
- return Boolean(currentSchema?.attributes?.[leafField]);
959
+ return checkPath(contentType, parts);
927
960
  }
928
961
  function getLeafAttributeSchema(contentType, allComponents, attribute) {
929
962
  if (!attribute) return null;
930
963
  if (!attribute.includes(".")) return contentType?.attributes?.[attribute] || null;
931
964
  const parts = attribute.split(".");
932
- let currentSchema = contentType;
933
- for (let i = 0; i < parts.length - 1; i++) {
934
- const part = parts[i];
935
- const attrDef = currentSchema?.attributes?.[part];
936
- if (!attrDef || attrDef.type !== "component" || !attrDef.component) return null;
937
- currentSchema = allComponents[attrDef.component];
938
- if (!currentSchema) return null;
965
+ function resolveLeaf(currentSchema, pathParts) {
966
+ if (!currentSchema || !currentSchema.attributes) return null;
967
+ const [head, ...tail] = pathParts;
968
+ const attrDef = currentSchema.attributes[head];
969
+ if (!attrDef) return null;
970
+ if (tail.length === 0) {
971
+ return attrDef;
972
+ }
973
+ if (attrDef.type === "component" && attrDef.component) {
974
+ return resolveLeaf(allComponents[attrDef.component], tail);
975
+ }
976
+ if (attrDef.type === "dynamiczone") {
977
+ const compUids = Array.isArray(attrDef.components) ? attrDef.components : [];
978
+ for (const compUid of compUids) {
979
+ const found = resolveLeaf(allComponents[compUid], tail);
980
+ if (found) return found;
981
+ }
982
+ }
983
+ return null;
939
984
  }
940
- return currentSchema?.attributes?.[parts[parts.length - 1]] || null;
985
+ return resolveLeaf(contentType, parts);
941
986
  }
942
987
  function setDeepProperty(obj, pathParts, value) {
943
988
  if (pathParts.length === 0) return value;
944
- const current = { ...obj || {} };
945
989
  const [head, ...tail] = pathParts;
990
+ if (Array.isArray(obj)) {
991
+ if (/^\d+$/.test(head)) {
992
+ const idx = parseInt(head, 10);
993
+ const copy = [...obj];
994
+ copy[idx] = setDeepProperty(copy[idx] || {}, tail, value);
995
+ return copy;
996
+ }
997
+ return obj.map((item) => {
998
+ if (!item || typeof item !== "object") return item;
999
+ if (head in item || tail.length > 0 && typeof item[head] === "object") {
1000
+ return {
1001
+ ...item,
1002
+ [head]: setDeepProperty(item[head], tail, value)
1003
+ };
1004
+ }
1005
+ return item;
1006
+ });
1007
+ }
1008
+ const current = { ...obj || {} };
946
1009
  if (tail.length === 0) {
947
1010
  current[head] = value;
948
1011
  } else {
949
- current[head] = setDeepProperty(current[head] || {}, tail, value);
1012
+ const subTarget = current[head];
1013
+ if (Array.isArray(subTarget)) {
1014
+ current[head] = setDeepProperty(subTarget, tail, value);
1015
+ } else {
1016
+ current[head] = setDeepProperty(subTarget || {}, tail, value);
1017
+ }
950
1018
  }
951
1019
  return current;
952
1020
  }
953
1021
  function getDeepProperty(obj, path2) {
954
1022
  if (!obj || !path2) return void 0;
955
- if (!path2.includes(".")) return obj[path2];
956
- return path2.split(".").reduce((acc, part) => acc && typeof acc === "object" ? acc[part] : void 0, obj);
1023
+ const parts = path2.split(".");
1024
+ function extract(current, remainingParts) {
1025
+ if (current === void 0 || current === null) return void 0;
1026
+ if (remainingParts.length === 0) return current;
1027
+ const [head, ...tail] = remainingParts;
1028
+ if (Array.isArray(current)) {
1029
+ if (/^\d+$/.test(head)) {
1030
+ return extract(current[parseInt(head, 10)], tail);
1031
+ }
1032
+ for (const item of current) {
1033
+ const found = extract(item?.[head], tail);
1034
+ if (found !== void 0 && found !== null) return found;
1035
+ }
1036
+ return void 0;
1037
+ }
1038
+ return extract(current[head], tail);
1039
+ }
1040
+ return extract(obj, parts);
957
1041
  }
958
1042
  function buildDeepFilter(pathParts, condition) {
959
1043
  if (pathParts.length === 0) return condition;
@@ -1183,7 +1267,8 @@ async function executeBatchInternal(strapi2, {
1183
1267
  findOneArgs.populate = populateOption;
1184
1268
  }
1185
1269
  const currentEntryWithPopulate = await strapi2.documents(target.uid).findOne(findOneArgs);
1186
- const existingRoot = currentEntryWithPopulate?.[rootKey] || {};
1270
+ const isRootDz = contentType?.attributes?.[rootKey]?.type === "dynamiczone";
1271
+ const existingRoot = currentEntryWithPopulate?.[rootKey] || (isRootDz ? [] : {});
1187
1272
  const currentVal2 = getDeepProperty(existingRoot, parts.slice(1).join("."));
1188
1273
  let newValue2 = value;
1189
1274
  if (regexObj && typeof currentVal2 === "string") {
@@ -1663,10 +1748,29 @@ const bulkUpdaterService = ({ strapi: strapi2 }) => {
1663
1748
  const nestedComponentAttrs = [];
1664
1749
  const validAttrs = Object.keys(rawAttrs).filter((attrKey) => {
1665
1750
  if (systemFields.includes(attrKey)) return false;
1666
- if (rawAttrs[attrKey].type === "dynamiczone") return false;
1751
+ if (rawAttrs[attrKey].type === "dynamiczone") {
1752
+ const dzDef = rawAttrs[attrKey];
1753
+ const dzComponents = Array.isArray(dzDef.components) ? dzDef.components : [];
1754
+ for (const compUid of dzComponents) {
1755
+ const compAttrs = discoverComponentAttributes(
1756
+ strapi2,
1757
+ compUid,
1758
+ attrKey,
1759
+ 1,
1760
+ 5,
1761
+ systemFields,
1762
+ allContentTypes,
1763
+ relationsByAttr,
1764
+ allRelationsMeta,
1765
+ colAttributesMeta
1766
+ );
1767
+ nestedComponentAttrs.push(...compAttrs);
1768
+ }
1769
+ return false;
1770
+ }
1667
1771
  if (rawAttrs[attrKey].type === "component") {
1668
1772
  const compDef = rawAttrs[attrKey];
1669
- if (!compDef.repeatable && compDef.component) {
1773
+ if (compDef.component) {
1670
1774
  const compAttrs = discoverComponentAttributes(
1671
1775
  strapi2,
1672
1776
  compDef.component,
@@ -3630,15 +3734,70 @@ function extractHeadingsFromRichText(content) {
3630
3734
  }
3631
3735
  return counts;
3632
3736
  }
3737
+ function cleanKey(str2) {
3738
+ return (str2 || "").replace(/[-_]/g, "").toLowerCase().trim();
3739
+ }
3633
3740
  function matchesPath(normalizedPath, leafKey, targetField) {
3634
3741
  if (!targetField) return false;
3635
- const pLower = (normalizedPath || "").toLowerCase().trim();
3636
- const kLower = (leafKey || "").toLowerCase().trim();
3637
- const tLower = targetField.toLowerCase().trim();
3638
- if (!tLower.includes(".")) {
3639
- return kLower === tLower || pLower === tLower || pLower.endsWith(`.${tLower}`);
3742
+ const pClean = cleanKey(normalizedPath);
3743
+ const kClean = cleanKey(leafKey);
3744
+ const tClean = cleanKey(targetField);
3745
+ if (kClean === tClean || pClean === tClean) return true;
3746
+ const targetSegments = targetField.replace(/^api::/i, "").split(".").map(cleanKey).filter(Boolean);
3747
+ const pathSegments = normalizedPath.split(".").map(cleanKey).filter(Boolean);
3748
+ if (targetSegments.length === 0 || pathSegments.length === 0) return false;
3749
+ if (targetSegments.length === 1) {
3750
+ const single = targetSegments[0];
3751
+ return kClean === single || pathSegments[pathSegments.length - 1] === single;
3752
+ }
3753
+ let isSuffix = true;
3754
+ for (let i = 0; i < targetSegments.length; i++) {
3755
+ const tSeg = targetSegments[targetSegments.length - 1 - i];
3756
+ const pSeg = pathSegments[pathSegments.length - 1 - i];
3757
+ if (tSeg !== "*" && tSeg !== pSeg) {
3758
+ isSuffix = false;
3759
+ break;
3760
+ }
3640
3761
  }
3641
- return pLower === tLower || pLower.endsWith(`.${tLower}`) || pLower.endsWith(tLower);
3762
+ if (isSuffix) return true;
3763
+ for (let startIdx = 1; startIdx < targetSegments.length; startIdx++) {
3764
+ const subTarget = targetSegments.slice(startIdx);
3765
+ if (subTarget.length > pathSegments.length) continue;
3766
+ let subSuffix = true;
3767
+ for (let i = 0; i < subTarget.length; i++) {
3768
+ const tSeg = subTarget[subTarget.length - 1 - i];
3769
+ const pSeg = pathSegments[pathSegments.length - 1 - i];
3770
+ if (tSeg !== "*" && tSeg !== pSeg) {
3771
+ subSuffix = false;
3772
+ break;
3773
+ }
3774
+ }
3775
+ if (subSuffix) return true;
3776
+ }
3777
+ let pIdx = 0;
3778
+ let matchesInOrder = true;
3779
+ let tStart = 0;
3780
+ if (targetSegments.length > 2 && targetSegments[0] !== pathSegments[0] && targetSegments[0] !== "*") {
3781
+ tStart = 1;
3782
+ }
3783
+ for (let tIdx = tStart; tIdx < targetSegments.length; tIdx++) {
3784
+ const tSeg = targetSegments[tIdx];
3785
+ if (tSeg === "*") continue;
3786
+ let found = false;
3787
+ while (pIdx < pathSegments.length) {
3788
+ if (pathSegments[pIdx] === tSeg) {
3789
+ found = true;
3790
+ pIdx++;
3791
+ break;
3792
+ }
3793
+ pIdx++;
3794
+ }
3795
+ if (!found) {
3796
+ matchesInOrder = false;
3797
+ break;
3798
+ }
3799
+ }
3800
+ return matchesInOrder;
3642
3801
  }
3643
3802
  const validationRulesService = ({ strapi: strapi2 }) => ({
3644
3803
  async getStore() {
@@ -3752,10 +3911,13 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3752
3911
  function componentHasField(compUid, targetField2) {
3753
3912
  const comp = allComponents[compUid];
3754
3913
  if (!comp?.attributes) return false;
3914
+ if (matchesPath(compUid, compUid, targetField2)) return true;
3755
3915
  for (const [attrName, attrDef] of Object.entries(comp.attributes)) {
3756
- if (matchesPath(attrName, attrName, targetField2)) return true;
3916
+ if (matchesPath(attrName, attrName, targetField2) || matchesPath(`${compUid}.${attrName}`, attrName, targetField2)) return true;
3757
3917
  if (attrDef.type === "component" && attrDef.component) {
3758
3918
  if (componentHasField(attrDef.component, targetField2)) return true;
3919
+ } else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
3920
+ if (attrDef.components.some((c) => componentHasField(c, targetField2))) return true;
3759
3921
  }
3760
3922
  }
3761
3923
  return false;
@@ -3772,15 +3934,15 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3772
3934
  if (attrDef.type === "component" && attrDef.component) {
3773
3935
  if (componentHasField(attrDef.component, targetField)) {
3774
3936
  hasField = true;
3775
- populateAttrs[attrKey] = true;
3937
+ populateAttrs[attrKey] = { populate: "*" };
3776
3938
  }
3777
3939
  } else if (attrDef.type === "dynamiczone" && Array.isArray(attrDef.components)) {
3778
3940
  const matchingComp = attrDef.components.some(
3779
- (compUid) => componentHasField(compUid, targetField)
3941
+ (compUid) => componentHasField(compUid, targetField) || matchesPath(`${attrKey}.${compUid}`, compUid, targetField)
3780
3942
  );
3781
3943
  if (matchingComp) {
3782
3944
  hasField = true;
3783
- populateAttrs[attrKey] = true;
3945
+ populateAttrs[attrKey] = { populate: "*" };
3784
3946
  }
3785
3947
  }
3786
3948
  }
@@ -3788,7 +3950,11 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3788
3950
  }
3789
3951
  const candidateUids = Object.keys(allContentTypes).filter((uid) => {
3790
3952
  if (!uid.startsWith("api::") || uid.includes("bulk-updater")) return false;
3791
- if (rule.collectionUid && rule.collectionUid !== "*" && rule.collectionUid !== uid) {
3953
+ if (rule.collectionUid && rule.collectionUid !== "*") {
3954
+ if (rule.collectionUid === uid) return true;
3955
+ const cleanRuleCol = cleanKey(rule.collectionUid);
3956
+ const cleanUid = cleanKey(uid);
3957
+ if (cleanUid.includes(cleanRuleCol) || cleanRuleCol.includes(cleanUid)) return true;
3792
3958
  return false;
3793
3959
  }
3794
3960
  return true;
@@ -3798,7 +3964,8 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3798
3964
  const ct = allContentTypes[uid];
3799
3965
  if (!ct) continue;
3800
3966
  const { hasField, populateAttrs } = schemaHasField(ct);
3801
- if (hasField || rule.collectionUid && rule.collectionUid === uid) {
3967
+ const isTargetCol = rule.collectionUid && (rule.collectionUid === uid || cleanKey(uid).includes(cleanKey(rule.collectionUid)));
3968
+ if (hasField || isTargetCol) {
3802
3969
  relevantCollections.push({
3803
3970
  uid,
3804
3971
  displayName: ct.info?.displayName || uid,
@@ -3821,11 +3988,15 @@ const validationRulesService = ({ strapi: strapi2 }) => ({
3821
3988
  for (const entry of entries) {
3822
3989
  let traverse = function(obj, currentPath = "") {
3823
3990
  if (!obj || typeof obj !== "object") return;
3991
+ const compName = typeof obj.__component === "string" ? obj.__component : "";
3824
3992
  for (const key of Object.keys(obj)) {
3993
+ if (key === "__component") continue;
3825
3994
  const val = obj[key];
3826
3995
  const newPath = currentPath ? `${currentPath}.${key}` : key;
3827
3996
  const normalizedPath = newPath.replace(/\.\d+/g, "");
3828
- if (matchesPath(normalizedPath, key, targetField) && typeof val === "string" && val.trim()) {
3997
+ const compPath = compName ? (currentPath ? `${currentPath}.${compName}.${key}` : `${compName}.${key}`).replace(/\.\d+/g, "") : "";
3998
+ const isMatch = matchesPath(normalizedPath, key, targetField) || (compPath ? matchesPath(compPath, key, targetField) : false);
3999
+ if (isMatch && typeof val === "string" && val.trim()) {
3829
4000
  const lower = val.toLowerCase().trim();
3830
4001
  structuredCounts[lower] = (structuredCounts[lower] || 0) + 1;
3831
4002
  }
@@ -34158,12 +34329,16 @@ const index = {
34158
34329
  if (data && typeof data === "object") {
34159
34330
  let scanDataTree = function(obj, currentPath = "") {
34160
34331
  if (!obj || typeof obj !== "object") return;
34332
+ const compName = typeof obj.__component === "string" ? obj.__component : "";
34161
34333
  for (const key of Object.keys(obj)) {
34334
+ if (key === "__component") continue;
34162
34335
  const val = obj[key];
34163
34336
  const newPath = currentPath ? `${currentPath}.${key}` : key;
34164
34337
  const normalizedPath = newPath.replace(/\.\d+/g, "");
34338
+ const compPath = compName ? (currentPath ? `${currentPath}.${compName}.${key}` : `${compName}.${key}`).replace(/\.\d+/g, "") : "";
34165
34339
  for (const rule of effectiveRules) {
34166
- if (matchesPath(normalizedPath, key, rule.targetField) && typeof val === "string" && val.trim()) {
34340
+ const isMatch = matchesPath(normalizedPath, key, rule.targetField) || (compPath ? matchesPath(compPath, key, rule.targetField) : false);
34341
+ if (isMatch && typeof val === "string" && val.trim()) {
34167
34342
  const lowerVal = val.toLowerCase().trim();
34168
34343
  structuredCounts[rule.id] = structuredCounts[rule.id] || {};
34169
34344
  structuredCounts[rule.id][lowerVal] = (structuredCounts[rule.id][lowerVal] || 0) + 1;
@@ -34201,7 +34376,13 @@ const index = {
34201
34376
  strapi2.log.warn("[bulk-updater] Failed to fetch active UI validation rules:", err);
34202
34377
  }
34203
34378
  const effectiveRules = [
34204
- ...activeUiRules.filter((r) => !r.collectionUid || r.collectionUid === "*" || r.collectionUid === context.uid).map((r) => ({
34379
+ ...activeUiRules.filter((r) => {
34380
+ if (!r.collectionUid || r.collectionUid === "*") return true;
34381
+ if (r.collectionUid === context.uid) return true;
34382
+ const cleanRuleCol = cleanKey(r.collectionUid);
34383
+ const cleanCtxCol = cleanKey(context.uid);
34384
+ return cleanCtxCol.includes(cleanRuleCol) || cleanRuleCol.includes(cleanCtxCol);
34385
+ }).map((r) => ({
34205
34386
  id: r.id || r.targetField,
34206
34387
  name: r.name || r.targetField,
34207
34388
  collectionUid: r.collectionUid || "*",