contentful-import 10.5.0 → 10.5.1

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 (3) hide show
  1. package/dist/index.js +131 -41
  2. package/dist/index.mjs +131 -41
  3. package/package.json +1 -1
package/dist/index.js CHANGED
@@ -5984,6 +5984,29 @@ var PARENT_FOLDER_GROUP_IDS = {
5984
5984
  fragment: "contentful.folder-group-fragment",
5985
5985
  experience: "contentful.folder-group-experience"
5986
5986
  };
5987
+ var VERSION_CONFLICT_MAX_ATTEMPTS = 5;
5988
+ var VERSION_CONFLICT_RETRY_DELAY_MS = 250;
5989
+ var MissingExoFolderGroupSchemesError = class extends Error {
5990
+ constructor(missingSchemeIds) {
5991
+ super(`Missing ExO folder-group concept scheme(s) in the destination organization: ${missingSchemeIds.join(", ")}`);
5992
+ this.name = "MissingExoFolderGroupSchemesError";
5993
+ this.missingSchemeIds = missingSchemeIds;
5994
+ }
5995
+ };
5996
+ var SourceExoFolderConceptReadError = class extends Error {
5997
+ constructor(sourceConceptId, error) {
5998
+ const reason = error instanceof Error ? error.message : String(error);
5999
+ super(`Unable to read source ExO folder concept ${sourceConceptId}: ${reason}`);
6000
+ this.name = "SourceExoFolderConceptReadError";
6001
+ this.sourceConceptId = sourceConceptId;
6002
+ }
6003
+ };
6004
+ var SourceOrganizationResolutionError = class extends Error {
6005
+ constructor(message) {
6006
+ super(message);
6007
+ this.name = "SourceOrganizationResolutionError";
6008
+ }
6009
+ };
5987
6010
  var ENTITY_TYPE_TO_PARENT_GROUP_ID = {
5988
6011
  designTokens: PARENT_FOLDER_GROUP_IDS.designToken,
5989
6012
  components: PARENT_FOLDER_GROUP_IDS.componentType,
@@ -6000,19 +6023,44 @@ function getSourceSpaceId(sourceEntities) {
6000
6023
  }
6001
6024
  return void 0;
6002
6025
  }
6003
- async function ensureParentFolderGroupsExist(client, organizationId) {
6026
+ function isVersionMismatchError(err) {
6027
+ return err?.error?.sys?.id === "VersionMismatch";
6028
+ }
6029
+ async function waitForVersionConflictRetry(attempt) {
6030
+ await new Promise((resolve2) => setTimeout(resolve2, VERSION_CONFLICT_RETRY_DELAY_MS * attempt));
6031
+ }
6032
+ async function resolveSourceOrganizationId(client, sourceSpaceId) {
6033
+ if (!sourceSpaceId) {
6034
+ throw new SourceOrganizationResolutionError(
6035
+ "Unable to resolve the source organization because the exported ExO entity does not include a source space ID"
6036
+ );
6037
+ }
6038
+ try {
6039
+ const sourceSpace = await client.space.get({ spaceId: sourceSpaceId });
6040
+ const sourceOrganizationId = sourceSpace?.sys?.organization?.sys?.id;
6041
+ if (!sourceOrganizationId) {
6042
+ throw new Error(`source space ${sourceSpaceId} has no organization link`);
6043
+ }
6044
+ return sourceOrganizationId;
6045
+ } catch (err) {
6046
+ if (err instanceof SourceOrganizationResolutionError) throw err;
6047
+ const reason = err instanceof Error ? err.message : String(err);
6048
+ throw new SourceOrganizationResolutionError(
6049
+ `Unable to resolve the source organization for space ${sourceSpaceId}: ${reason}`
6050
+ );
6051
+ }
6052
+ }
6053
+ async function ensureParentFolderGroupsExist(client, destinationOrganizationId, requiredParentGroupIds) {
6054
+ if (requiredParentGroupIds.size === 0) return /* @__PURE__ */ new Map();
6004
6055
  const { items } = await client.conceptScheme.getMany({
6005
- organizationId,
6056
+ organizationId: destinationOrganizationId,
6006
6057
  query: { purpose: "internal" }
6007
6058
  });
6008
6059
  const existingIds = new Set(items.map((s) => s.sys.id));
6009
- const allParentFolderGroupsExist = Object.values(PARENT_FOLDER_GROUP_IDS).every((id) => existingIds.has(id));
6010
- if (!allParentFolderGroupsExist) {
6011
- return /* @__PURE__ */ new Map();
6012
- }
6013
- const parentGroupIdSet = new Set(Object.values(PARENT_FOLDER_GROUP_IDS));
6060
+ const missingSchemeIds = [...requiredParentGroupIds].filter((id) => !existingIds.has(id));
6061
+ if (missingSchemeIds.length > 0) throw new MissingExoFolderGroupSchemesError(missingSchemeIds);
6014
6062
  return new Map(
6015
- items.filter((s) => parentGroupIdSet.has(s.sys.id)).map((s) => [s.sys.id, s])
6063
+ items.filter((s) => requiredParentGroupIds.has(s.sys.id)).map((s) => [s.sys.id, s])
6016
6064
  );
6017
6065
  }
6018
6066
  function deriveChildConceptMap(sourceEntities, destinationSpaceId) {
@@ -6031,27 +6079,48 @@ function deriveChildConceptMap(sourceEntities, destinationSpaceId) {
6031
6079
  }
6032
6080
  return childConceptMap;
6033
6081
  }
6034
- async function createOrPatchChildConcepts(client, organizationId, destinationSpaceId, childConceptMap) {
6082
+ async function createOrPatchChildConcepts(client, sourceOrganizationId, destinationOrganizationId, destinationSpaceId, childConceptMap) {
6035
6083
  const spaceLink = { sys: { type: "Link", linkType: "Space", id: destinationSpaceId } };
6084
+ const existingDestinationConcepts = /* @__PURE__ */ new Map();
6036
6085
  for (const [sourceConceptId, { destConceptId }] of childConceptMap) {
6037
- let prefLabel = { "en-US": destConceptId };
6038
- try {
6039
- const sourceConcept = await client.concept.get({ organizationId, conceptId: sourceConceptId });
6040
- if (sourceConcept?.prefLabel) prefLabel = sourceConcept.prefLabel;
6041
- } catch {
6042
- }
6043
6086
  let existing = null;
6044
6087
  try {
6045
- existing = await client.concept.get({ organizationId, conceptId: destConceptId });
6088
+ existing = await client.concept.get({
6089
+ organizationId: destinationOrganizationId,
6090
+ conceptId: destConceptId
6091
+ });
6046
6092
  } catch (err) {
6047
6093
  if (err?.name !== "NotFound") {
6048
6094
  import_logging10.logEmitter.emit("warning", `Could not fetch destination child concept ${destConceptId}: ${err?.message ?? err}`);
6049
6095
  }
6050
6096
  }
6097
+ existingDestinationConcepts.set(sourceConceptId, existing);
6098
+ }
6099
+ const sourceConceptLabels = /* @__PURE__ */ new Map();
6100
+ for (const [sourceConceptId] of childConceptMap) {
6101
+ if (existingDestinationConcepts.get(sourceConceptId)) {
6102
+ continue;
6103
+ }
6104
+ try {
6105
+ const sourceConcept = await client.concept.get({
6106
+ organizationId: sourceOrganizationId,
6107
+ conceptId: sourceConceptId
6108
+ });
6109
+ if (!sourceConcept?.prefLabel) {
6110
+ throw new Error("source concept has no prefLabel");
6111
+ }
6112
+ sourceConceptLabels.set(sourceConceptId, sourceConcept.prefLabel);
6113
+ } catch (err) {
6114
+ throw new SourceExoFolderConceptReadError(sourceConceptId, err);
6115
+ }
6116
+ }
6117
+ for (const [sourceConceptId, { destConceptId }] of childConceptMap) {
6118
+ const existing = existingDestinationConcepts.get(sourceConceptId);
6051
6119
  if (!existing) {
6120
+ const prefLabel = sourceConceptLabels.get(sourceConceptId);
6052
6121
  try {
6053
6122
  await client.concept.createWithId(
6054
- { organizationId, conceptId: destConceptId },
6123
+ { organizationId: destinationOrganizationId, conceptId: destConceptId },
6055
6124
  // @ts-expect-error - CMA.js type needs to be updated to be aware of purpose: 'internal'
6056
6125
  { purpose: "internal", prefLabel, metadata: { spaces: [spaceLink] } }
6057
6126
  );
@@ -6068,7 +6137,7 @@ async function createOrPatchChildConcepts(client, organizationId, destinationSpa
6068
6137
  if (patches.length > 0) {
6069
6138
  try {
6070
6139
  await client.concept.patch(
6071
- { organizationId, conceptId: destConceptId, version: existing.sys.version },
6140
+ { organizationId: destinationOrganizationId, conceptId: destConceptId, version: existing.sys.version },
6072
6141
  patches
6073
6142
  );
6074
6143
  import_logging10.logEmitter.emit("info", `Patched child folder concept ${destConceptId} (${patches.map((p) => p.path).join(", ")})`);
@@ -6079,21 +6148,33 @@ async function createOrPatchChildConcepts(client, organizationId, destinationSpa
6079
6148
  }
6080
6149
  }
6081
6150
  }
6082
- async function linkChildConceptsToParentGroups(client, organizationId, childConceptMap, parentGroups) {
6151
+ async function linkChildConceptsToParentGroups(client, destinationOrganizationId, childConceptMap, parentGroups) {
6083
6152
  for (const [, { destConceptId, parentGroupId }] of childConceptMap) {
6084
- const parentGroup = parentGroups.get(parentGroupId);
6085
- if (!parentGroup) continue;
6086
- const alreadyLinked = (parentGroup.concepts ?? []).some((c) => c.sys.id === destConceptId);
6087
- if (alreadyLinked) continue;
6088
- try {
6089
- const updated = await client.conceptScheme.patch(
6090
- { organizationId, conceptSchemeId: parentGroupId, version: parentGroup.sys.version },
6091
- [{ op: "add", path: "/concepts/-", value: { sys: { type: "Link", linkType: "TaxonomyConcept", id: destConceptId } } }]
6092
- );
6093
- parentGroups.set(parentGroupId, updated);
6094
- import_logging10.logEmitter.emit("info", `Linked child concept ${destConceptId} to parent group ${parentGroupId}`);
6095
- } catch (err) {
6096
- import_logging10.logEmitter.emit("error", `Failed to link child concept ${destConceptId} to parent group ${parentGroupId}: ${err?.message ?? err}`);
6153
+ let parentGroup = parentGroups.get(parentGroupId);
6154
+ for (let attempt = 1; parentGroup && attempt <= VERSION_CONFLICT_MAX_ATTEMPTS; attempt++) {
6155
+ const alreadyLinked = (parentGroup.concepts ?? []).some((c) => c.sys.id === destConceptId);
6156
+ if (alreadyLinked) break;
6157
+ try {
6158
+ const updated = await client.conceptScheme.patch(
6159
+ { organizationId: destinationOrganizationId, conceptSchemeId: parentGroupId, version: parentGroup.sys.version },
6160
+ [{ op: "add", path: "/concepts/-", value: { sys: { type: "Link", linkType: "TaxonomyConcept", id: destConceptId } } }]
6161
+ );
6162
+ parentGroups.set(parentGroupId, updated);
6163
+ import_logging10.logEmitter.emit("info", `Linked child concept ${destConceptId} to parent group ${parentGroupId}`);
6164
+ break;
6165
+ } catch (err) {
6166
+ if (!isVersionMismatchError(err) || attempt === VERSION_CONFLICT_MAX_ATTEMPTS) {
6167
+ import_logging10.logEmitter.emit("error", `Failed to link child concept ${destConceptId} to parent group ${parentGroupId}: ${err?.message ?? err}`);
6168
+ break;
6169
+ }
6170
+ import_logging10.logEmitter.emit("warning", `Version mismatch linking child concept ${destConceptId} to parent group ${parentGroupId}; retrying`);
6171
+ await waitForVersionConflictRetry(attempt);
6172
+ parentGroup = await client.conceptScheme.get({
6173
+ organizationId: destinationOrganizationId,
6174
+ conceptSchemeId: parentGroupId
6175
+ });
6176
+ parentGroups.set(parentGroupId, parentGroup);
6177
+ }
6097
6178
  }
6098
6179
  }
6099
6180
  }
@@ -6109,7 +6190,7 @@ function rewriteEntityFolderConcepts(entities2, childConceptMap) {
6109
6190
  }
6110
6191
  async function importExoFolders({
6111
6192
  client,
6112
- organizationId,
6193
+ destinationOrganizationId,
6113
6194
  destinationSpaceId,
6114
6195
  sourceEntities
6115
6196
  }) {
@@ -6118,16 +6199,22 @@ async function importExoFolders({
6118
6199
  import_logging10.logEmitter.emit("info", "Source and destination space are the same \u2014 skipping ExO folder import");
6119
6200
  return;
6120
6201
  }
6121
- const parentGroups = await ensureParentFolderGroupsExist(client, organizationId);
6122
- if (parentGroups.size === 0) {
6123
- import_logging10.logEmitter.emit("warn", "One or more Experience Orchestration folder group concept schemes are missing in the destination organization. Please create them before importing.");
6124
- return;
6125
- }
6126
6202
  const childConceptMap = deriveChildConceptMap(sourceEntities, destinationSpaceId);
6127
6203
  if (childConceptMap.size === 0) return;
6204
+ const requiredParentGroupIds = new Set(
6205
+ [...childConceptMap.values()].map(({ parentGroupId }) => parentGroupId)
6206
+ );
6207
+ const sourceOrganizationId = await resolveSourceOrganizationId(client, sourceSpaceId);
6208
+ const parentGroups = await ensureParentFolderGroupsExist(client, destinationOrganizationId, requiredParentGroupIds);
6128
6209
  import_logging10.logEmitter.emit("info", `Importing ${childConceptMap.size} ExO folder concept(s) into destination space ${destinationSpaceId}`);
6129
- await createOrPatchChildConcepts(client, organizationId, destinationSpaceId, childConceptMap);
6130
- await linkChildConceptsToParentGroups(client, organizationId, childConceptMap, parentGroups);
6210
+ await createOrPatchChildConcepts(
6211
+ client,
6212
+ sourceOrganizationId,
6213
+ destinationOrganizationId,
6214
+ destinationSpaceId,
6215
+ childConceptMap
6216
+ );
6217
+ await linkChildConceptsToParentGroups(client, destinationOrganizationId, childConceptMap, parentGroups);
6131
6218
  const allEntities = [
6132
6219
  ...sourceEntities.designTokens ?? [],
6133
6220
  ...sourceEntities.components ?? [],
@@ -6557,7 +6644,7 @@ function pushToSpace({
6557
6644
  const space = await client.space.get({ spaceId });
6558
6645
  await importExoFolders({
6559
6646
  client,
6560
- organizationId: space.sys.organization.sys.id,
6647
+ destinationOrganizationId: space.sys.organization.sys.id,
6561
6648
  destinationSpaceId: spaceId,
6562
6649
  sourceEntities: {
6563
6650
  designTokens: sourceData.designTokens,
@@ -6568,6 +6655,9 @@ function pushToSpace({
6568
6655
  }
6569
6656
  });
6570
6657
  } catch (error) {
6658
+ if (error instanceof MissingExoFolderGroupSchemesError || error instanceof SourceOrganizationResolutionError || error instanceof SourceExoFolderConceptReadError) {
6659
+ throw error;
6660
+ }
6571
6661
  import_logging12.logEmitter.emit("warning", `Unable to create Experience Orchestration (ExO) folders, error: ${error}`);
6572
6662
  }
6573
6663
  }),
package/dist/index.mjs CHANGED
@@ -5951,6 +5951,29 @@ var PARENT_FOLDER_GROUP_IDS = {
5951
5951
  fragment: "contentful.folder-group-fragment",
5952
5952
  experience: "contentful.folder-group-experience"
5953
5953
  };
5954
+ var VERSION_CONFLICT_MAX_ATTEMPTS = 5;
5955
+ var VERSION_CONFLICT_RETRY_DELAY_MS = 250;
5956
+ var MissingExoFolderGroupSchemesError = class extends Error {
5957
+ constructor(missingSchemeIds) {
5958
+ super(`Missing ExO folder-group concept scheme(s) in the destination organization: ${missingSchemeIds.join(", ")}`);
5959
+ this.name = "MissingExoFolderGroupSchemesError";
5960
+ this.missingSchemeIds = missingSchemeIds;
5961
+ }
5962
+ };
5963
+ var SourceExoFolderConceptReadError = class extends Error {
5964
+ constructor(sourceConceptId, error) {
5965
+ const reason = error instanceof Error ? error.message : String(error);
5966
+ super(`Unable to read source ExO folder concept ${sourceConceptId}: ${reason}`);
5967
+ this.name = "SourceExoFolderConceptReadError";
5968
+ this.sourceConceptId = sourceConceptId;
5969
+ }
5970
+ };
5971
+ var SourceOrganizationResolutionError = class extends Error {
5972
+ constructor(message) {
5973
+ super(message);
5974
+ this.name = "SourceOrganizationResolutionError";
5975
+ }
5976
+ };
5954
5977
  var ENTITY_TYPE_TO_PARENT_GROUP_ID = {
5955
5978
  designTokens: PARENT_FOLDER_GROUP_IDS.designToken,
5956
5979
  components: PARENT_FOLDER_GROUP_IDS.componentType,
@@ -5967,19 +5990,44 @@ function getSourceSpaceId(sourceEntities) {
5967
5990
  }
5968
5991
  return void 0;
5969
5992
  }
5970
- async function ensureParentFolderGroupsExist(client, organizationId) {
5993
+ function isVersionMismatchError(err) {
5994
+ return err?.error?.sys?.id === "VersionMismatch";
5995
+ }
5996
+ async function waitForVersionConflictRetry(attempt) {
5997
+ await new Promise((resolve2) => setTimeout(resolve2, VERSION_CONFLICT_RETRY_DELAY_MS * attempt));
5998
+ }
5999
+ async function resolveSourceOrganizationId(client, sourceSpaceId) {
6000
+ if (!sourceSpaceId) {
6001
+ throw new SourceOrganizationResolutionError(
6002
+ "Unable to resolve the source organization because the exported ExO entity does not include a source space ID"
6003
+ );
6004
+ }
6005
+ try {
6006
+ const sourceSpace = await client.space.get({ spaceId: sourceSpaceId });
6007
+ const sourceOrganizationId = sourceSpace?.sys?.organization?.sys?.id;
6008
+ if (!sourceOrganizationId) {
6009
+ throw new Error(`source space ${sourceSpaceId} has no organization link`);
6010
+ }
6011
+ return sourceOrganizationId;
6012
+ } catch (err) {
6013
+ if (err instanceof SourceOrganizationResolutionError) throw err;
6014
+ const reason = err instanceof Error ? err.message : String(err);
6015
+ throw new SourceOrganizationResolutionError(
6016
+ `Unable to resolve the source organization for space ${sourceSpaceId}: ${reason}`
6017
+ );
6018
+ }
6019
+ }
6020
+ async function ensureParentFolderGroupsExist(client, destinationOrganizationId, requiredParentGroupIds) {
6021
+ if (requiredParentGroupIds.size === 0) return /* @__PURE__ */ new Map();
5971
6022
  const { items } = await client.conceptScheme.getMany({
5972
- organizationId,
6023
+ organizationId: destinationOrganizationId,
5973
6024
  query: { purpose: "internal" }
5974
6025
  });
5975
6026
  const existingIds = new Set(items.map((s) => s.sys.id));
5976
- const allParentFolderGroupsExist = Object.values(PARENT_FOLDER_GROUP_IDS).every((id) => existingIds.has(id));
5977
- if (!allParentFolderGroupsExist) {
5978
- return /* @__PURE__ */ new Map();
5979
- }
5980
- const parentGroupIdSet = new Set(Object.values(PARENT_FOLDER_GROUP_IDS));
6027
+ const missingSchemeIds = [...requiredParentGroupIds].filter((id) => !existingIds.has(id));
6028
+ if (missingSchemeIds.length > 0) throw new MissingExoFolderGroupSchemesError(missingSchemeIds);
5981
6029
  return new Map(
5982
- items.filter((s) => parentGroupIdSet.has(s.sys.id)).map((s) => [s.sys.id, s])
6030
+ items.filter((s) => requiredParentGroupIds.has(s.sys.id)).map((s) => [s.sys.id, s])
5983
6031
  );
5984
6032
  }
5985
6033
  function deriveChildConceptMap(sourceEntities, destinationSpaceId) {
@@ -5998,27 +6046,48 @@ function deriveChildConceptMap(sourceEntities, destinationSpaceId) {
5998
6046
  }
5999
6047
  return childConceptMap;
6000
6048
  }
6001
- async function createOrPatchChildConcepts(client, organizationId, destinationSpaceId, childConceptMap) {
6049
+ async function createOrPatchChildConcepts(client, sourceOrganizationId, destinationOrganizationId, destinationSpaceId, childConceptMap) {
6002
6050
  const spaceLink = { sys: { type: "Link", linkType: "Space", id: destinationSpaceId } };
6051
+ const existingDestinationConcepts = /* @__PURE__ */ new Map();
6003
6052
  for (const [sourceConceptId, { destConceptId }] of childConceptMap) {
6004
- let prefLabel = { "en-US": destConceptId };
6005
- try {
6006
- const sourceConcept = await client.concept.get({ organizationId, conceptId: sourceConceptId });
6007
- if (sourceConcept?.prefLabel) prefLabel = sourceConcept.prefLabel;
6008
- } catch {
6009
- }
6010
6053
  let existing = null;
6011
6054
  try {
6012
- existing = await client.concept.get({ organizationId, conceptId: destConceptId });
6055
+ existing = await client.concept.get({
6056
+ organizationId: destinationOrganizationId,
6057
+ conceptId: destConceptId
6058
+ });
6013
6059
  } catch (err) {
6014
6060
  if (err?.name !== "NotFound") {
6015
6061
  import_logging10.logEmitter.emit("warning", `Could not fetch destination child concept ${destConceptId}: ${err?.message ?? err}`);
6016
6062
  }
6017
6063
  }
6064
+ existingDestinationConcepts.set(sourceConceptId, existing);
6065
+ }
6066
+ const sourceConceptLabels = /* @__PURE__ */ new Map();
6067
+ for (const [sourceConceptId] of childConceptMap) {
6068
+ if (existingDestinationConcepts.get(sourceConceptId)) {
6069
+ continue;
6070
+ }
6071
+ try {
6072
+ const sourceConcept = await client.concept.get({
6073
+ organizationId: sourceOrganizationId,
6074
+ conceptId: sourceConceptId
6075
+ });
6076
+ if (!sourceConcept?.prefLabel) {
6077
+ throw new Error("source concept has no prefLabel");
6078
+ }
6079
+ sourceConceptLabels.set(sourceConceptId, sourceConcept.prefLabel);
6080
+ } catch (err) {
6081
+ throw new SourceExoFolderConceptReadError(sourceConceptId, err);
6082
+ }
6083
+ }
6084
+ for (const [sourceConceptId, { destConceptId }] of childConceptMap) {
6085
+ const existing = existingDestinationConcepts.get(sourceConceptId);
6018
6086
  if (!existing) {
6087
+ const prefLabel = sourceConceptLabels.get(sourceConceptId);
6019
6088
  try {
6020
6089
  await client.concept.createWithId(
6021
- { organizationId, conceptId: destConceptId },
6090
+ { organizationId: destinationOrganizationId, conceptId: destConceptId },
6022
6091
  // @ts-expect-error - CMA.js type needs to be updated to be aware of purpose: 'internal'
6023
6092
  { purpose: "internal", prefLabel, metadata: { spaces: [spaceLink] } }
6024
6093
  );
@@ -6035,7 +6104,7 @@ async function createOrPatchChildConcepts(client, organizationId, destinationSpa
6035
6104
  if (patches.length > 0) {
6036
6105
  try {
6037
6106
  await client.concept.patch(
6038
- { organizationId, conceptId: destConceptId, version: existing.sys.version },
6107
+ { organizationId: destinationOrganizationId, conceptId: destConceptId, version: existing.sys.version },
6039
6108
  patches
6040
6109
  );
6041
6110
  import_logging10.logEmitter.emit("info", `Patched child folder concept ${destConceptId} (${patches.map((p) => p.path).join(", ")})`);
@@ -6046,21 +6115,33 @@ async function createOrPatchChildConcepts(client, organizationId, destinationSpa
6046
6115
  }
6047
6116
  }
6048
6117
  }
6049
- async function linkChildConceptsToParentGroups(client, organizationId, childConceptMap, parentGroups) {
6118
+ async function linkChildConceptsToParentGroups(client, destinationOrganizationId, childConceptMap, parentGroups) {
6050
6119
  for (const [, { destConceptId, parentGroupId }] of childConceptMap) {
6051
- const parentGroup = parentGroups.get(parentGroupId);
6052
- if (!parentGroup) continue;
6053
- const alreadyLinked = (parentGroup.concepts ?? []).some((c) => c.sys.id === destConceptId);
6054
- if (alreadyLinked) continue;
6055
- try {
6056
- const updated = await client.conceptScheme.patch(
6057
- { organizationId, conceptSchemeId: parentGroupId, version: parentGroup.sys.version },
6058
- [{ op: "add", path: "/concepts/-", value: { sys: { type: "Link", linkType: "TaxonomyConcept", id: destConceptId } } }]
6059
- );
6060
- parentGroups.set(parentGroupId, updated);
6061
- import_logging10.logEmitter.emit("info", `Linked child concept ${destConceptId} to parent group ${parentGroupId}`);
6062
- } catch (err) {
6063
- import_logging10.logEmitter.emit("error", `Failed to link child concept ${destConceptId} to parent group ${parentGroupId}: ${err?.message ?? err}`);
6120
+ let parentGroup = parentGroups.get(parentGroupId);
6121
+ for (let attempt = 1; parentGroup && attempt <= VERSION_CONFLICT_MAX_ATTEMPTS; attempt++) {
6122
+ const alreadyLinked = (parentGroup.concepts ?? []).some((c) => c.sys.id === destConceptId);
6123
+ if (alreadyLinked) break;
6124
+ try {
6125
+ const updated = await client.conceptScheme.patch(
6126
+ { organizationId: destinationOrganizationId, conceptSchemeId: parentGroupId, version: parentGroup.sys.version },
6127
+ [{ op: "add", path: "/concepts/-", value: { sys: { type: "Link", linkType: "TaxonomyConcept", id: destConceptId } } }]
6128
+ );
6129
+ parentGroups.set(parentGroupId, updated);
6130
+ import_logging10.logEmitter.emit("info", `Linked child concept ${destConceptId} to parent group ${parentGroupId}`);
6131
+ break;
6132
+ } catch (err) {
6133
+ if (!isVersionMismatchError(err) || attempt === VERSION_CONFLICT_MAX_ATTEMPTS) {
6134
+ import_logging10.logEmitter.emit("error", `Failed to link child concept ${destConceptId} to parent group ${parentGroupId}: ${err?.message ?? err}`);
6135
+ break;
6136
+ }
6137
+ import_logging10.logEmitter.emit("warning", `Version mismatch linking child concept ${destConceptId} to parent group ${parentGroupId}; retrying`);
6138
+ await waitForVersionConflictRetry(attempt);
6139
+ parentGroup = await client.conceptScheme.get({
6140
+ organizationId: destinationOrganizationId,
6141
+ conceptSchemeId: parentGroupId
6142
+ });
6143
+ parentGroups.set(parentGroupId, parentGroup);
6144
+ }
6064
6145
  }
6065
6146
  }
6066
6147
  }
@@ -6076,7 +6157,7 @@ function rewriteEntityFolderConcepts(entities2, childConceptMap) {
6076
6157
  }
6077
6158
  async function importExoFolders({
6078
6159
  client,
6079
- organizationId,
6160
+ destinationOrganizationId,
6080
6161
  destinationSpaceId,
6081
6162
  sourceEntities
6082
6163
  }) {
@@ -6085,16 +6166,22 @@ async function importExoFolders({
6085
6166
  import_logging10.logEmitter.emit("info", "Source and destination space are the same \u2014 skipping ExO folder import");
6086
6167
  return;
6087
6168
  }
6088
- const parentGroups = await ensureParentFolderGroupsExist(client, organizationId);
6089
- if (parentGroups.size === 0) {
6090
- import_logging10.logEmitter.emit("warn", "One or more Experience Orchestration folder group concept schemes are missing in the destination organization. Please create them before importing.");
6091
- return;
6092
- }
6093
6169
  const childConceptMap = deriveChildConceptMap(sourceEntities, destinationSpaceId);
6094
6170
  if (childConceptMap.size === 0) return;
6171
+ const requiredParentGroupIds = new Set(
6172
+ [...childConceptMap.values()].map(({ parentGroupId }) => parentGroupId)
6173
+ );
6174
+ const sourceOrganizationId = await resolveSourceOrganizationId(client, sourceSpaceId);
6175
+ const parentGroups = await ensureParentFolderGroupsExist(client, destinationOrganizationId, requiredParentGroupIds);
6095
6176
  import_logging10.logEmitter.emit("info", `Importing ${childConceptMap.size} ExO folder concept(s) into destination space ${destinationSpaceId}`);
6096
- await createOrPatchChildConcepts(client, organizationId, destinationSpaceId, childConceptMap);
6097
- await linkChildConceptsToParentGroups(client, organizationId, childConceptMap, parentGroups);
6177
+ await createOrPatchChildConcepts(
6178
+ client,
6179
+ sourceOrganizationId,
6180
+ destinationOrganizationId,
6181
+ destinationSpaceId,
6182
+ childConceptMap
6183
+ );
6184
+ await linkChildConceptsToParentGroups(client, destinationOrganizationId, childConceptMap, parentGroups);
6098
6185
  const allEntities = [
6099
6186
  ...sourceEntities.designTokens ?? [],
6100
6187
  ...sourceEntities.components ?? [],
@@ -6524,7 +6611,7 @@ function pushToSpace({
6524
6611
  const space = await client.space.get({ spaceId });
6525
6612
  await importExoFolders({
6526
6613
  client,
6527
- organizationId: space.sys.organization.sys.id,
6614
+ destinationOrganizationId: space.sys.organization.sys.id,
6528
6615
  destinationSpaceId: spaceId,
6529
6616
  sourceEntities: {
6530
6617
  designTokens: sourceData.designTokens,
@@ -6535,6 +6622,9 @@ function pushToSpace({
6535
6622
  }
6536
6623
  });
6537
6624
  } catch (error) {
6625
+ if (error instanceof MissingExoFolderGroupSchemesError || error instanceof SourceOrganizationResolutionError || error instanceof SourceExoFolderConceptReadError) {
6626
+ throw error;
6627
+ }
6538
6628
  import_logging12.logEmitter.emit("warning", `Unable to create Experience Orchestration (ExO) folders, error: ${error}`);
6539
6629
  }
6540
6630
  }),
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "contentful-import",
3
- "version": "10.5.0",
3
+ "version": "10.5.1",
4
4
  "description": "this tool allows you to import JSON dump exported by contentful-export",
5
5
  "main": "dist/index.mjs",
6
6
  "typings": "dist/index.d.ts",