@adobe/aio-commerce-lib-app 1.3.1 → 1.5.0

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 (62) hide show
  1. package/CHANGELOG.md +53 -0
  2. package/dist/cjs/actions/app-config.cjs +3 -3
  3. package/dist/cjs/actions/app-config.d.cts +1 -1
  4. package/dist/cjs/actions/config.cjs +31 -9
  5. package/dist/cjs/actions/installation.cjs +16 -13
  6. package/dist/cjs/actions/installation.d.cts +2 -2
  7. package/dist/cjs/actions/registration.cjs +41 -0
  8. package/dist/cjs/actions/registration.d.cts +33 -0
  9. package/dist/cjs/actions/scope-tree.cjs +5 -4
  10. package/dist/cjs/admin-ui-sdk-BiSopnKI.d.cts +665 -0
  11. package/dist/cjs/{app-DcQMhW2N.d.cts → app-C6kImBl7.d.cts} +242 -16
  12. package/dist/cjs/chunk-DS2yp5vX.cjs +48 -0
  13. package/dist/cjs/commands/index.cjs +582 -321
  14. package/dist/cjs/commands/templates/admin-ui-sdk/registration.js.template +20 -0
  15. package/dist/cjs/config/index.cjs +4 -3
  16. package/dist/cjs/config/index.d.cts +558 -62
  17. package/dist/cjs/{config-BppBKCFj.cjs → config-ct-qMOGj.cjs} +56 -25
  18. package/dist/cjs/{error-Dn7ool6k.cjs → error-C5LNOZ4f.cjs} +1 -1
  19. package/dist/cjs/{index-BVwQk9bx.d.cts → index-B5WKJ3hJ.d.cts} +20 -174
  20. package/dist/cjs/{logging-4s36JTiN.cjs → logging-BTevALnL.cjs} +2 -2
  21. package/dist/cjs/management/index.cjs +1 -1
  22. package/dist/cjs/management/index.d.cts +3 -2
  23. package/dist/cjs/{management-C6xG5bfl.cjs → management-D1QXg4YU.cjs} +128 -75
  24. package/dist/cjs/{router-KeQRduO3.cjs → router-CFErodHU.cjs} +2 -2
  25. package/dist/cjs/{schemas-nkIxa8sL.cjs → schemas-CukVpc19.cjs} +2 -34
  26. package/dist/cjs/step-CWYnKbga.d.cts +187 -0
  27. package/dist/cjs/{validate-CeUCT_7k.cjs → validate-b_bI4mDb.cjs} +4 -3
  28. package/dist/cjs/{webhooks-CLtDxwMa.cjs → webhooks-DS1BtgED.cjs} +157 -17
  29. package/dist/es/actions/app-config.d.mts +1 -1
  30. package/dist/es/actions/app-config.mjs +1 -1
  31. package/dist/es/actions/config.mjs +28 -7
  32. package/dist/es/actions/installation.d.mts +2 -2
  33. package/dist/es/actions/installation.mjs +12 -10
  34. package/dist/es/actions/registration.d.mts +33 -0
  35. package/dist/es/actions/registration.mjs +39 -0
  36. package/dist/es/actions/scope-tree.mjs +1 -1
  37. package/dist/es/admin-ui-sdk-CCo2SWiL.d.mts +667 -0
  38. package/dist/es/{app-DJr-mN9d.d.mts → app-DcY6dJiQ.d.mts} +242 -16
  39. package/dist/es/commands/index.mjs +575 -316
  40. package/dist/es/commands/templates/admin-ui-sdk/registration.js.template +20 -0
  41. package/dist/es/config/index.d.mts +558 -62
  42. package/dist/es/config/index.mjs +4 -4
  43. package/dist/es/{config-BohwKkQS.mjs → config-DGPFTk6w.mjs} +51 -20
  44. package/dist/es/{index-BENO5T7n.d.mts → index-CCov0odd.d.mts} +20 -174
  45. package/dist/es/management/index.d.mts +3 -2
  46. package/dist/es/management/index.mjs +1 -1
  47. package/dist/es/{management-ByHvVJ12.mjs → management-CCsOfniv.mjs} +124 -71
  48. package/dist/es/step-0eI2x4C9.d.mts +187 -0
  49. package/dist/es/{validate-CqJdGzyZ.mjs → validate-Dn3lLTGj.mjs} +2 -2
  50. package/dist/es/{webhooks-CYo-pqbR.mjs → webhooks-Dhrkkip7.mjs} +155 -16
  51. package/package.json +15 -12
  52. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/app-config.js.template +0 -0
  53. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/custom-scripts.js.template +0 -0
  54. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/installation.js.template +0 -0
  55. /package/dist/cjs/commands/{generate/actions/templates → templates}/business-configuration/config.js.template +0 -0
  56. /package/dist/cjs/commands/{generate/actions/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  57. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/app-config.js.template +0 -0
  58. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/custom-scripts.js.template +0 -0
  59. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/installation.js.template +0 -0
  60. /package/dist/es/commands/{generate/actions/templates → templates}/business-configuration/config.js.template +0 -0
  61. /package/dist/es/commands/{generate/actions/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  62. /package/dist/es/{schemas-CVXHgUhv.mjs → schemas-DOcnD16x.mjs} +0 -0
@@ -12,16 +12,16 @@
12
12
  * governing permissions and limitations under the License.
13
13
  */
14
14
 
15
- import { a as hasCustomInstallationSteps, c as hasEventing, d as hasAdminUiSdk, l as hasExternalEvents, n as hasWebhooks, s as hasCommerceEvents } from "./webhooks-CYo-pqbR.mjs";
15
+ import { a as hasCustomInstallationSteps, c as hasEventing, d as hasAdminUiSdk, l as hasExternalEvents, n as hasWebhooks, s as hasCommerceEvents } from "./webhooks-Dhrkkip7.mjs";
16
16
  import { t as stringifyError } from "./error-DHlYzkbb.mjs";
17
17
  import { t as inspect } from "./logging-XIUXDK5T.mjs";
18
+ import { unwrapHttpError } from "@adobe/aio-commerce-lib-api/utils";
18
19
  import camelcase from "camelcase";
19
20
  import { AdobeCommerceHttpClient, resolveCommerceHttpClientParams, resolveIoEventsHttpClientParams } from "@adobe/aio-commerce-lib-api";
20
21
  import { resolveAuthParams, resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";
21
22
  import { createCustomCommerceEventsApiClient, createEventProvider, createEventSubscription, deleteEventProvider, deleteEventSubscription, getAllEventProviders, getAllEventSubscriptions, updateEventingConfiguration } from "@adobe/aio-commerce-lib-events/commerce";
22
23
  import { createCustomAdobeIoEventsApiClient, createEventMetadataForProvider, createEventProvider as createEventProvider$1, createRegistration, deleteEventMetadataForProvider, deleteEventProvider as deleteEventProvider$1, deleteRegistration, getAllEventProviders as getAllEventProviders$1, getAllRegistrations } from "@adobe/aio-commerce-lib-events/io-events";
23
24
  import { createCustomCommerceWebhooksApiClient, getWebhookList, subscribeWebhook, unsubscribeWebhook } from "@adobe/aio-commerce-lib-webhooks/api";
24
- import { HTTPError } from "ky";
25
25
 
26
26
  //#region source/management/installation/workflow/hooks.ts
27
27
  /** Helper to call a hook if it exists. */
@@ -120,11 +120,11 @@ function getAtPath(data, path) {
120
120
  return current;
121
121
  }
122
122
  /** Creates an installation error from an exception. */
123
- function createInstallationError(err, path, key = "STEP_EXECUTION_FAILED") {
123
+ async function createInstallationError(err, path, key = "STEP_EXECUTION_FAILED") {
124
124
  return {
125
125
  path,
126
126
  key,
127
- message: err instanceof Error ? err.message : String(err)
127
+ message: await unwrapHttpError(err)
128
128
  };
129
129
  }
130
130
  /** Creates a succeeded installation state. */
@@ -164,6 +164,28 @@ function createInitialState(options) {
164
164
  };
165
165
  }
166
166
  /**
167
+ * Creates a retry state from a failed state.
168
+ * Preserves succeeded steps and their data so the workflow resumes from
169
+ * the failed step rather than restarting from scratch.
170
+ */
171
+ function createRetryState(failedState) {
172
+ return {
173
+ id: failedState.id,
174
+ startedAt: failedState.startedAt,
175
+ status: "in-progress",
176
+ step: resetFailedSteps(failedState.step),
177
+ data: failedState.data
178
+ };
179
+ }
180
+ /** Recursively resets non-succeeded steps back to "pending". */
181
+ function resetFailedSteps(step) {
182
+ return {
183
+ ...step,
184
+ status: step.status === "succeeded" ? "succeeded" : "pending",
185
+ children: step.children.map(resetFailedSteps)
186
+ };
187
+ }
188
+ /**
167
189
  * Executes a workflow from an initial state. Returns the final state (never throws).
168
190
  */
169
191
  async function executeWorkflow(options) {
@@ -188,7 +210,7 @@ async function executeWorkflowWithMode(options, mode) {
188
210
  id: initialState.id,
189
211
  startedAt: initialState.startedAt,
190
212
  step,
191
- data: null,
213
+ data: initialState.data,
192
214
  error: null,
193
215
  hooks,
194
216
  mode
@@ -205,7 +227,7 @@ async function executeWorkflowWithMode(options, mode) {
205
227
  await callHook(hooks, "onInstallationSuccess", succeeded);
206
228
  return succeeded;
207
229
  } catch (err) {
208
- const error = context.error ?? createInstallationError(err, [], "INSTALLATION_FAILED");
230
+ const error = context.error ?? await createInstallationError(err, [], "INSTALLATION_FAILED");
209
231
  const failed = createFailedState({
210
232
  step: context.step,
211
233
  id: context.id,
@@ -248,6 +270,7 @@ function snapshot(context) {
248
270
  }
249
271
  /** Executes a single step (branch or leaf) recursively. */
250
272
  async function executeStep(step, stepStatus, inherited, context) {
273
+ if (stepStatus.status === "succeeded") return;
251
274
  const { path } = stepStatus;
252
275
  const isLeaf = isLeafStep(step);
253
276
  stepStatus.status = "in-progress";
@@ -269,7 +292,7 @@ async function executeStep(step, stepStatus, inherited, context) {
269
292
  }, snapshot(context));
270
293
  } catch (err) {
271
294
  stepStatus.status = "failed";
272
- context.error ??= createInstallationError(err, path);
295
+ context.error ??= await createInstallationError(err, path);
273
296
  await callHook(context.hooks, "onStepFailure", {
274
297
  path,
275
298
  stepName: step.name,
@@ -567,6 +590,19 @@ function defineCustomInstallationStep(handlerOrDefinition) {
567
590
  return handlerOrDefinition;
568
591
  }
569
592
 
593
+ //#endregion
594
+ //#region source/management/installation/utils/http-error.ts
595
+ /**
596
+ * Unwraps an error via `unwrapHttpError`, prefixes it, logs it at error level,
597
+ * and throws a new `Error` with the enriched message. Returns `Promise<never>`
598
+ * so it composes cleanly inside `.catch()` without widening the surrounding type.
599
+ */
600
+ async function throwHttpError(logger, error, prefix) {
601
+ const message = `${prefix}: ${await unwrapHttpError(error)}`;
602
+ logger.error(message);
603
+ throw new Error(message);
604
+ }
605
+
570
606
  //#endregion
571
607
  //#region source/management/installation/admin-ui-sdk/helpers.ts
572
608
  /**
@@ -583,7 +619,7 @@ async function registerExtension(context) {
583
619
  extensionTitle: appData.projectTitle,
584
620
  extensionUrl: `https://${process.env.__OW_NAMESPACE}.adobeio-static.net/index.html`,
585
621
  extensionWorkspace: appData.workspaceName
586
- } } }).json();
622
+ } } }).json().catch((error) => throwHttpError(logger, error, "Failed to register Admin UI SDK extension"));
587
623
  logger.info(`Admin UI SDK extension registered successfully: ${response.extensionId}`);
588
624
  return response;
589
625
  }
@@ -602,7 +638,8 @@ async function uninstallExtension(context) {
602
638
  await commerceClient.delete(endpoint);
603
639
  logger.info(`Admin UI SDK extension "${extensionName}" unregistered successfully.`);
604
640
  } catch (error) {
605
- logger.warn(`Failed to unregister Admin UI SDK extension "${extensionName}": ${stringifyError(error)}. Continuing uninstall.`);
641
+ const msg = await unwrapHttpError(error);
642
+ logger.warn(`Failed to unregister Admin UI SDK extension "${extensionName}": ${msg}. Continuing uninstall.`);
606
643
  }
607
644
  }
608
645
 
@@ -924,10 +961,7 @@ async function createIoEventProvider(params) {
924
961
  }).then((res) => {
925
962
  logger.info(`Provider "${provider.label}" created with ID '${res.id}'`);
926
963
  return res;
927
- }).catch((error) => {
928
- logger.error(`Failed to create provider "${provider.label}": ${stringifyError(error)}`);
929
- throw error;
930
- });
964
+ }).catch((error) => throwHttpError(logger, error, `Failed to create I/O Events provider '${provider.label}'`));
931
965
  }
932
966
  /**
933
967
  * Creates or retrieves an existing I/O Events provider.
@@ -974,10 +1008,7 @@ async function createIoEventProviderEventMetadata(params) {
974
1008
  }).then((res) => {
975
1009
  logger.info(`Event metadata "${event.label}" created for provider "${provider.label}"`);
976
1010
  return res;
977
- }).catch((error) => {
978
- logger.error(`Failed to create event metadata "${event.label}" for provider "${provider.label}": ${stringifyError(error)}`);
979
- throw error;
980
- });
1011
+ }).catch((error) => throwHttpError(logger, error, `Failed to register I/O Events metadata for '${eventCode}'`));
981
1012
  }
982
1013
  /**
983
1014
  * Creates or retrieves existing I/O Events metadata for a given provider and event.
@@ -1025,10 +1056,7 @@ async function createIoEventRegistration(params) {
1025
1056
  return ioEventsClient.createRegistration(payload).then((res) => {
1026
1057
  logger.info(`Registration "${name}" created for provider "${provider.label}" with ID '${res.id}'`);
1027
1058
  return res;
1028
- }).catch((error) => {
1029
- logger.error(`Failed to create registration "${name}" for provider "${provider.label}": ${stringifyError(error)}`);
1030
- throw error;
1031
- });
1059
+ }).catch((error) => throwHttpError(logger, error, `Failed to create I/O Events registration '${name}'`));
1032
1060
  }
1033
1061
  /**
1034
1062
  * Creates or retrieves an existing I/O Events registration.
@@ -1067,10 +1095,7 @@ async function configureCommerceEventing(params, existingData) {
1067
1095
  return;
1068
1096
  }
1069
1097
  throw new Error("Something went wrong while configuring Commerce Eventing Module. Response was not successful but no error was thrown.");
1070
- }).catch((err) => {
1071
- logger.error(`Failed to configure Commerce Eventing Module: ${stringifyError(err)}`);
1072
- throw err;
1073
- });
1098
+ }).catch((err) => throwHttpError(logger, err, "Failed to configure Adobe Commerce eventing"));
1074
1099
  }
1075
1100
  /**
1076
1101
  * Creates an event provider in Commerce for a given {@link IoEventProvider}.
@@ -1086,24 +1111,29 @@ async function createCommerceProvider(params) {
1086
1111
  }).then((res) => {
1087
1112
  logger.info(`Commerce provider "${provider.label}" created with ID '${res.provider_id}'`);
1088
1113
  return res;
1089
- }).catch((err) => {
1090
- logger.error(`Failed to create Commerce provider "${provider.label}": ${stringifyError(err)}`);
1091
- throw err;
1092
- });
1114
+ }).catch((err) => throwHttpError(logger, err, `Failed to create Adobe Commerce event provider '${provider.label}'`));
1093
1115
  }
1094
1116
  /**
1095
1117
  * Creates or retrieves an existing Commerce event provider.
1118
+ *
1119
+ * Checks by `provider_id` first, since Commerce validates subscriptions against that field.
1120
+ * A previous partial run may have left multiple records sharing the same `instance_id` but
1121
+ * with different `provider_id` values; checking by `provider_id` first avoids re-creating a
1122
+ * provider that is already correctly registered.
1123
+ *
1096
1124
  * @param params - Parameters needed to create or get the Commerce provider.
1097
- * @param existingData - Existing Commerce providers.
1125
+ * @param existingProviders - Existing Commerce providers.
1098
1126
  */
1099
1127
  async function createOrGetCommerceProvider(params, existingProviders) {
1100
1128
  const { context, provider } = params;
1101
1129
  const { logger } = context;
1102
- const existing = findExistingProvider(existingProviders, provider.instance_id);
1103
- if (existing) {
1104
- logger.info(`Provider "${provider.label}" already exists with ID "${existing.id}", skipping creation.`);
1105
- return existing;
1130
+ const existingByProviderId = existingProviders.find((p) => p.provider_id === provider.id);
1131
+ if (existingByProviderId) {
1132
+ logger.info(`Provider "${provider.label}" already exists with provider ID "${provider.id}", skipping creation.`);
1133
+ return existingByProviderId;
1106
1134
  }
1135
+ const existingByInstanceId = findExistingProvider(existingProviders, provider.instance_id);
1136
+ if (existingByInstanceId) logger.warn(`Provider "${provider.label}" exists (instance ID: ${provider.instance_id}) but with a different provider ID ("${existingByInstanceId.provider_id}" → "${provider.id}"). Registering updated provider in Commerce.`);
1107
1137
  return createCommerceProvider(params);
1108
1138
  }
1109
1139
  /**
@@ -1129,10 +1159,7 @@ async function createCommerceEventSubscription(params) {
1129
1159
  return commerceEventsClient.createEventSubscription(eventSpec).then((_res) => {
1130
1160
  logger.info(`Created event subscription for event "${event.config.name}" to provider "${provider.label} (instance ID: ${provider.instance_id})"`);
1131
1161
  return eventSpec;
1132
- }).catch((err) => {
1133
- logger.error(`Failed to create event subscription for event "${event.config.name}" to provider "${provider.label}": ${stringifyError(err)}`);
1134
- throw err;
1135
- });
1162
+ }).catch((err) => throwHttpError(logger, err, `Failed to create Adobe Commerce event subscription for '${event.config.name}'`));
1136
1163
  }
1137
1164
  /**
1138
1165
  * Creates or retrieves an existing Commerce event subscription.
@@ -1262,7 +1289,8 @@ async function deleteIoEventRegistrations(providerData, provider, events, regist
1262
1289
  });
1263
1290
  logger.info(`Deleted registration "${registration.name}" (ID: ${registration.id}).`);
1264
1291
  } catch (error) {
1265
- logger.warn(`Failed to delete registration "${registration.name}" (ID: ${registration.id}): ${stringifyError(error)}. Continuing uninstall.`);
1292
+ const msg = await unwrapHttpError(error);
1293
+ logger.warn(`Failed to delete I/O Events registration "${registration.name}" (ID: ${registration.id}): ${msg}. Continuing uninstall.`);
1266
1294
  }
1267
1295
  }
1268
1296
  }
@@ -1293,7 +1321,8 @@ async function deleteIoEventMetadata(providerData, provider, context) {
1293
1321
  });
1294
1322
  logger.info(`Deleted event metadata "${eventMetadata.event_code}" from provider "${providerData.id}".`);
1295
1323
  } catch (error) {
1296
- logger.warn(`Failed to delete event metadata "${eventMetadata.event_code}" from provider "${providerData.id}": ${stringifyError(error)}. Continuing uninstall.`);
1324
+ const msg = await unwrapHttpError(error);
1325
+ logger.warn(`Failed to delete I/O Events metadata "${eventMetadata.event_code}" from provider "${providerData.id}": ${msg}. Continuing uninstall.`);
1297
1326
  }
1298
1327
  }
1299
1328
  }
@@ -1316,7 +1345,8 @@ async function deleteIoEventProvider(providerData, provider, context) {
1316
1345
  });
1317
1346
  logger.info(`Deleted I/O Events provider "${provider.label}" (ID: ${providerData.id}).`);
1318
1347
  } catch (error) {
1319
- logger.warn(`Failed to delete I/O Events provider "${provider.label}" (ID: ${providerData.id}): ${stringifyError(error)}. Continuing uninstall.`);
1348
+ const msg = await unwrapHttpError(error);
1349
+ logger.warn(`Failed to delete I/O Events provider "${provider.label}" (ID: ${providerData.id}): ${msg}. Continuing uninstall.`);
1320
1350
  }
1321
1351
  }
1322
1352
  /**
@@ -1364,7 +1394,8 @@ async function deleteCommerceEventSubscriptions(events, metadata, provider, exis
1364
1394
  await commerceEventsClient.deleteEventSubscription({ name: eventName });
1365
1395
  logger.info(`Unsubscribed Commerce event subscription for "${eventName}".`);
1366
1396
  } catch (error) {
1367
- logger.warn(`Failed to unsubscribe Commerce event subscription for "${eventName}": ${stringifyError(error)}. Continuing uninstall.`);
1397
+ const msg = await unwrapHttpError(error);
1398
+ logger.warn(`Failed to unsubscribe Adobe Commerce event subscription for "${eventName}": ${msg}. Continuing uninstall.`);
1368
1399
  }
1369
1400
  }
1370
1401
  }
@@ -1387,7 +1418,8 @@ async function deleteCommerceEventProvider(metadata, provider, existingProviders
1387
1418
  await commerceEventsClient.deleteEventProvider({ provider_id: commerceProvider.provider_id });
1388
1419
  logger.info(`Deleted Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id}).`);
1389
1420
  } catch (error) {
1390
- logger.warn(`Failed to delete Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id}): ${stringifyError(error)}. Continuing uninstall.`);
1421
+ const msg = await unwrapHttpError(error);
1422
+ logger.warn(`Failed to delete Adobe Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id}): ${msg}. Continuing uninstall.`);
1391
1423
  }
1392
1424
  }
1393
1425
  /**
@@ -1474,15 +1506,13 @@ async function createCommerceEvents(config, context) {
1474
1506
  data: {
1475
1507
  ioEvents: providerData,
1476
1508
  commerce: commerceProvider,
1477
- events: eventsData.map(({ config, data }, index) => {
1478
- return {
1479
- config,
1480
- data: {
1481
- ...data,
1482
- subscription: subscriptions[index]
1483
- }
1484
- };
1485
- })
1509
+ events: eventsData.map(({ config, data }, index) => ({
1510
+ config,
1511
+ data: {
1512
+ ...data,
1513
+ subscription: subscriptions[index]
1514
+ }
1515
+ }))
1486
1516
  }
1487
1517
  } });
1488
1518
  }
@@ -1802,7 +1832,7 @@ async function deleteWebhookSubscriptions(config, context) {
1802
1832
  logger.info(`Unsubscribed webhook: ${getWebhookName(webhook)}`);
1803
1833
  unsubscribedWebhooks.push(params);
1804
1834
  } catch (error) {
1805
- logger.warn(`Failed to unsubscribe webhook "${getWebhookName(webhook)}": ${stringifyError(error)}. Continuing uninstall.`);
1835
+ logger.warn(`${stringifyError(error)}. Continuing uninstall.`);
1806
1836
  }
1807
1837
  }
1808
1838
  logger.info(`Webhook unsubscriptions complete: ${unsubscribedWebhooks.length} unsubscribed.`);
@@ -1822,20 +1852,12 @@ async function createOrGetWebhookSubscription(existingWebhooks, client, resolved
1822
1852
  return subscribed;
1823
1853
  }
1824
1854
  /**
1825
- * Re-throws `err`, enriching the message with the webhook name if the error is an
1826
- * `HTTPError` with a JSON body containing a string `message` field.
1855
+ * Re-throws `err` as a new Error with an enriched message that includes the webhook name
1856
+ * and the unwrapped HTTP response body (if available).
1827
1857
  */
1828
1858
  async function rethrowWithWebhookName(err, webhookName, operation) {
1829
- if (err instanceof HTTPError) {
1830
- let body;
1831
- try {
1832
- body = await err.response.json();
1833
- } catch {
1834
- throw err;
1835
- }
1836
- if (typeof body?.message === "string") throw new Error(`Webhook ${operation} failed for "${webhookName}": ${body.message}`);
1837
- }
1838
- throw err;
1859
+ const msg = await unwrapHttpError(err);
1860
+ throw new Error(`Failed to ${operation} webhook subscription for "${webhookName}": ${msg}`);
1839
1861
  }
1840
1862
  /**
1841
1863
  * Subscribes a single webhook to Commerce, enriching the error with the webhook name
@@ -1846,7 +1868,7 @@ async function createWebhookSubscription(client, resolvedWebhook) {
1846
1868
  await client.subscribeWebhook(resolvedWebhook);
1847
1869
  return resolvedWebhook;
1848
1870
  } catch (err) {
1849
- return await rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "subscription");
1871
+ return await rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "create");
1850
1872
  }
1851
1873
  }
1852
1874
  /**
@@ -1857,7 +1879,7 @@ async function deleteWebhookSubscription(client, resolvedWebhook, params) {
1857
1879
  try {
1858
1880
  await client.unsubscribeWebhook(params);
1859
1881
  } catch (err) {
1860
- await rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "unsubscription");
1882
+ return rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "delete");
1861
1883
  }
1862
1884
  }
1863
1885
  /**
@@ -2032,16 +2054,47 @@ function createInitialInstallationState(options) {
2032
2054
  }
2033
2055
  /**
2034
2056
  * Runs the full installation workflow. Returns the final state (never throws).
2057
+ *
2058
+ * Retries once on failure. `onInstallationFailure` only fires if both attempts fail;
2059
+ * `isRetry: true` is set on the result when the retry succeeds.
2035
2060
  */
2036
- function runInstallation(options) {
2061
+ async function runInstallation(options) {
2037
2062
  const { installationContext, config, initialState, hooks } = options;
2038
- return executeWorkflow({
2039
- rootStep: createRootInstallationStep(config),
2063
+ const rootStep = createRootInstallationStep(config);
2064
+ const firstResult = await executeWorkflow({
2065
+ rootStep,
2040
2066
  installationContext,
2041
2067
  config,
2042
2068
  initialState,
2043
- hooks
2069
+ hooks: {
2070
+ ...hooks,
2071
+ onInstallationFailure: void 0,
2072
+ onStepFailure: void 0
2073
+ }
2044
2074
  });
2075
+ if (firstResult.status === "succeeded") return firstResult;
2076
+ const { error } = firstResult;
2077
+ installationContext.logger.warn(`Installation attempt 1 failed: step "${error.path.join(".")}", key "${error.key}"${error.message ? `, message "${error.message}"` : ""}. Retrying once.`);
2078
+ return {
2079
+ ...await executeWorkflow({
2080
+ rootStep,
2081
+ installationContext,
2082
+ config,
2083
+ initialState: createRetryState(firstResult),
2084
+ hooks: hooks && {
2085
+ ...hooks,
2086
+ onInstallationSuccess: (state) => hooks.onInstallationSuccess?.({
2087
+ ...state,
2088
+ metadata: { isRetry: true }
2089
+ }),
2090
+ onInstallationFailure: (state) => hooks.onInstallationFailure?.({
2091
+ ...state,
2092
+ metadata: { isRetry: true }
2093
+ })
2094
+ }
2095
+ }),
2096
+ metadata: { isRetry: true }
2097
+ };
2045
2098
  }
2046
2099
  /**
2047
2100
  * Creates an initial uninstallation state from the config and step definitions.
@@ -0,0 +1,187 @@
1
+ /**
2
+ * @license
3
+ *
4
+ * Copyright 2026 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ import { n as CommerceAppConfigOutputModel } from "./app-DcY6dJiQ.mjs";
16
+ import AioLogger from "@adobe/aio-lib-core-logging";
17
+ import * as v from "valibot";
18
+ import { RuntimeActionParams } from "@adobe/aio-commerce-lib-core/params";
19
+
20
+ //#region source/management/installation/schema.d.ts
21
+ /** Schema for validating Adobe I/O app credentials required for installation. */
22
+ declare const AppDataSchema: v.ObjectSchema<{
23
+ readonly consumerOrgId: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
24
+ readonly orgName: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
25
+ readonly projectId: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
26
+ readonly projectName: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
27
+ readonly projectTitle: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
28
+ readonly workspaceId: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
29
+ readonly workspaceName: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
30
+ readonly workspaceTitle: v.SchemaWithPipe<readonly [v.StringSchema<`Expected a string value for '${string}'`>, v.NonEmptyAction<string, `The value of "${string}" must not be empty`>]>;
31
+ }, undefined>;
32
+ /** Type for Adobe I/O app credentials. */
33
+ type AppData = v.InferOutput<typeof AppDataSchema>;
34
+ //#endregion
35
+ //#region source/management/installation/workflow/step.d.ts
36
+ /** Severity level of a validation issue. */
37
+ type ValidationIssueSeverity = "error" | "warning" | "info";
38
+ /** A single validation issue reported by a step's validate handler. */
39
+ type ValidationIssue = {
40
+ /** Machine-readable code identifying the issue type. */code: string; /** Human-readable description of the issue. */
41
+ message: string; /** Severity of the issue. Only "error" severity blocks installation. */
42
+ severity: ValidationIssueSeverity; /** Optional additional context about the issue. */
43
+ details?: Record<string, unknown>;
44
+ };
45
+ /** Shared context available to all steps during installation. */
46
+ type InstallationContext = {
47
+ /** The credentials of the app being installed */appData: AppData; /** The raw action parameters from the App Builder runtime action. */
48
+ params: RuntimeActionParams & {
49
+ AIO_COMMERCE_AUTH_IMS_CLIENT_ID: string;
50
+ AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS: string | string[];
51
+ AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID: string;
52
+ AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL: string;
53
+ AIO_COMMERCE_AUTH_IMS_ORG_ID: string;
54
+ AIO_COMMERCE_AUTH_IMS_SCOPES: string | string[];
55
+ }; /** Logger instance for installation logging. */
56
+ logger: ReturnType<typeof AioLogger>; /** Custom scripts defined in the configuration (if any). */
57
+ customScripts?: Record<string, unknown>;
58
+ };
59
+ /** Factory function type for creating step-specific context. */
60
+ type StepContextFactory<TStepCtx extends Record<string, unknown> = Record<string, unknown>> = (context: InstallationContext) => TStepCtx | Promise<TStepCtx>;
61
+ /** The execution context passed to leaf step run handlers. */
62
+ type ExecutionContext<TStepCtx extends Record<string, unknown> = Record<string, unknown>> = InstallationContext & TStepCtx;
63
+ /**
64
+ * A narrowed context available to step `validate` handlers.
65
+ * Excludes `customScripts` — those only apply during installation, not pre-flight validation.
66
+ */
67
+ type ValidationContext = Omit<InstallationContext, "customScripts">;
68
+ /** The context passed to step `validate` handlers (base validation context merged with step-level context). */
69
+ type ValidationExecutionContext<TStepCtx extends Record<string, unknown> = Record<string, unknown>> = ValidationContext & TStepCtx;
70
+ /** Metadata info for a step (used for UI display). */
71
+ type StepMetaInfo = {
72
+ label: string;
73
+ description?: string;
74
+ };
75
+ /** Step metadata keyed by execution mode. */
76
+ type StepMeta = {
77
+ install: StepMetaInfo;
78
+ uninstall?: StepMetaInfo;
79
+ };
80
+ /** Defines the base properties of a step. */
81
+ type StepBase<TName extends string = string, TConfig extends CommerceAppConfigOutputModel = CommerceAppConfigOutputModel> = {
82
+ /** The name of this step. */name: TName; /** Metadata associated with the step, keyed by execution mode. */
83
+ meta: StepMeta; /** Whether the step should be taken into consideration. */
84
+ when?: (config: CommerceAppConfigOutputModel) => config is TConfig;
85
+ };
86
+ /** A leaf step that executes work (no children). */
87
+ type LeafStep<TName extends string = string, TConfig extends CommerceAppConfigOutputModel = CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> = StepBase<TName, TConfig> & {
88
+ type: "leaf"; /** The execution handler for the step. */
89
+ install: (config: TConfig, context: ExecutionContext<TStepCtx>) => TOutput | Promise<TOutput>;
90
+ /**
91
+ * Optional pre-installation validation handler.
92
+ * Called before installation begins to surface issues (errors or warnings).
93
+ * Returning an empty array means the step has no issues.
94
+ */
95
+ validate?: (config: TConfig, context: ValidationExecutionContext<TStepCtx>) => ValidationIssue[] | Promise<ValidationIssue[]>;
96
+ /**
97
+ * Optional uninstall handler for the step.
98
+ * Called during uninstallation to reverse the work done by `install`.
99
+ * If absent, the step is silently skipped during uninstallation.
100
+ */
101
+ uninstall?: (config: TConfig, context: ExecutionContext<TStepCtx>) => void | Promise<void>;
102
+ };
103
+ /** A branch step that contains children (no execution). */
104
+ type BranchStep<TName extends string = string, TConfig extends CommerceAppConfigOutputModel = CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TChildren extends AnyStep[] = AnyStep[]> = StepBase<TName, TConfig> & {
105
+ type: "branch"; /** An optional factory function to setup shared context for the children steps. */
106
+ context?: StepContextFactory<TStepCtx>; /** The children steps of this branch. */
107
+ children: TChildren;
108
+ /**
109
+ * Optional pre-installation validation handler for the branch itself.
110
+ * Called before children are validated. Returning an empty array means
111
+ * the branch has no issues at this level.
112
+ */
113
+ validate?: (config: TConfig, context: ValidationExecutionContext<TStepCtx>) => ValidationIssue[] | Promise<ValidationIssue[]>;
114
+ };
115
+ /** A step in the installation tree (discriminated union by `type`). */
116
+ type Step<TName extends string = string, TConfig extends CommerceAppConfigOutputModel = CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> = LeafStep<TName, TConfig, TStepCtx, TOutput> | BranchStep<TName, TConfig, TStepCtx>;
117
+ /** Loosely-typed step for use in non type-safe contexts. */
118
+ interface AnyStep {
119
+ children?: AnyStep[];
120
+ context?: (context: InstallationContext) => any;
121
+ install?: (config: any, context: any) => unknown | Promise<unknown>;
122
+ meta: StepMeta;
123
+ name: string;
124
+ type: "leaf" | "branch";
125
+ uninstall?: (config: any, context: any) => void | Promise<void>;
126
+ validate?: (config: any, context: any) => ValidationIssue[] | Promise<ValidationIssue[]>;
127
+ when?: (config: CommerceAppConfigOutputModel) => boolean;
128
+ }
129
+ /** Check if a step is a leaf step. */
130
+ declare function isLeafStep(step: AnyStep): step is LeafStep;
131
+ /** Check if a step is a branch step. */
132
+ declare function isBranchStep(step: AnyStep): step is BranchStep;
133
+ /** Options for defining a leaf step. */
134
+ type LeafStepOptions<TName extends string, TConfig extends CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown> = Omit<LeafStep<TName, TConfig, TStepCtx, TOutput>, "type">;
135
+ /** Options for defining a branch step. */
136
+ type BranchStepOptions<TName extends string, TConfig extends CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TChildren extends AnyStep[] = AnyStep[]> = Omit<BranchStep<TName, TConfig, TStepCtx, TChildren>, "type">;
137
+ /**
138
+ * Define a leaf step (executable, no children).
139
+ *
140
+ * @example
141
+ * ```typescript
142
+ * const createProviders = defineLeafStep({
143
+ * name: "providers",
144
+ * meta: { install: { label: "Create Providers", description: "Creates I/O Events providers" } },
145
+ * install: async ({ config, stepContext }) => {
146
+ * const { eventsClient } = stepContext;
147
+ * return eventsClient.createProvider(config.eventing);
148
+ * },
149
+ * });
150
+ * ```
151
+ */
152
+ declare function defineLeafStep<TName extends string, TConfig extends CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, TOutput = unknown>(options: LeafStepOptions<TName, TConfig, TStepCtx, TOutput>): {
153
+ type: "leaf";
154
+ name: TName;
155
+ meta: StepMeta;
156
+ when: ((config: CommerceAppConfigOutputModel) => config is TConfig) | undefined;
157
+ install: (config: TConfig, context: ExecutionContext<TStepCtx>) => TOutput | Promise<TOutput>;
158
+ uninstall: ((config: TConfig, context: ExecutionContext<TStepCtx>) => void | Promise<void>) | undefined;
159
+ validate: ((config: TConfig, context: ValidationExecutionContext<TStepCtx>) => ValidationIssue[] | Promise<ValidationIssue[]>) | undefined;
160
+ };
161
+ /**
162
+ * Define a branch step (container with children, no runner).
163
+ *
164
+ * @example
165
+ * ```typescript
166
+ * const eventing = defineBranchStep({
167
+ * name: "eventing",
168
+ * meta: { install: { label: "Eventing", description: "Sets up I/O Events" } },
169
+ * when: hasEventing,
170
+ * context: async (ctx) => ({ eventsClient: await createEventsClient(ctx) }),
171
+ * children: [commerceEventsStep, externalEventsStep],
172
+ * });
173
+ * ```
174
+ */
175
+ declare function defineBranchStep<TName extends string, TConfig extends CommerceAppConfigOutputModel, TStepCtx extends Record<string, unknown> = Record<string, unknown>, const TChildren extends AnyStep[] = AnyStep[]>(options: BranchStepOptions<TName, TConfig, TStepCtx, TChildren>): {
176
+ type: "branch";
177
+ name: TName;
178
+ meta: StepMeta;
179
+ when: ((config: CommerceAppConfigOutputModel) => config is TConfig) | undefined;
180
+ context: StepContextFactory<TStepCtx> | undefined;
181
+ children: TChildren;
182
+ validate: ((config: TConfig, context: ValidationExecutionContext<TStepCtx>) => ValidationIssue[] | Promise<ValidationIssue[]>) | undefined;
183
+ };
184
+ /** Infer the output type from a leaf step. */
185
+ type InferStepOutput<TStep> = TStep extends LeafStep<infer _TName, infer _TConfig, infer _TStepCtx, infer TOutput> ? Awaited<TOutput> : never;
186
+ //#endregion
187
+ export { defineBranchStep as _, InferStepOutput as a, isLeafStep as b, LeafStepOptions as c, StepMeta as d, StepMetaInfo as f, ValidationIssueSeverity as g, ValidationIssue as h, ExecutionContext as i, Step as l, ValidationExecutionContext as m, BranchStep as n, InstallationContext as o, ValidationContext as p, BranchStepOptions as r, LeafStep as s, AnyStep as t, StepContextFactory as u, defineLeafStep as v, isBranchStep as y };
@@ -12,8 +12,8 @@
12
12
  * governing permissions and limitations under the License.
13
13
  */
14
14
 
15
- import { a as hasCustomInstallationSteps, d as hasAdminUiSdk, i as hasCustomInstallation, l as hasExternalEvents, n as hasWebhooks, o as EventingSchema, r as InstallationSchema, s as hasCommerceEvents, t as WebhooksSchema, u as AdminUiSdkSchema } from "./webhooks-CYo-pqbR.mjs";
16
- import { t as alphaNumericOrHyphenSchema } from "./schemas-CVXHgUhv.mjs";
15
+ import { t as alphaNumericOrHyphenSchema } from "./schemas-DOcnD16x.mjs";
16
+ import { a as hasCustomInstallationSteps, d as hasAdminUiSdk, i as hasCustomInstallation, l as hasExternalEvents, n as hasWebhooks, o as EventingSchema, r as InstallationSchema, s as hasCommerceEvents, t as WebhooksSchema, u as AdminUiSdkSchema } from "./webhooks-Dhrkkip7.mjs";
17
17
  import { CommerceSdkValidationError } from "@adobe/aio-commerce-lib-core/error";
18
18
  import * as v from "valibot";
19
19
  import { SchemaBusinessConfig } from "@adobe/aio-commerce-lib-config";