@adobe/aio-commerce-lib-app 1.2.0 → 1.3.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.
- package/CHANGELOG.md +16 -0
- package/dist/cjs/actions/config.cjs +32 -1
- package/dist/cjs/actions/installation.cjs +167 -43
- package/dist/cjs/actions/installation.d.cts +1 -1
- package/dist/cjs/{index-DZxladgt.d.cts → index-BVwQk9bx.d.cts} +81 -27
- package/dist/cjs/management/index.cjs +3 -1
- package/dist/cjs/management/index.d.cts +2 -2
- package/dist/cjs/{management-iLQubQ7K.cjs → management-C6xG5bfl.cjs} +661 -166
- package/dist/es/actions/config.mjs +32 -1
- package/dist/es/actions/installation.d.mts +1 -1
- package/dist/es/actions/installation.mjs +167 -43
- package/dist/es/{index-BmYXe7kp.d.mts → index-BENO5T7n.d.mts} +81 -27
- package/dist/es/management/index.d.mts +2 -2
- package/dist/es/management/index.mjs +2 -2
- package/dist/es/{management-DSexEPTW.mjs → management-ByHvVJ12.mjs} +653 -170
- package/package.json +5 -5
|
@@ -18,9 +18,9 @@ import { t as inspect } from "./logging-XIUXDK5T.mjs";
|
|
|
18
18
|
import camelcase from "camelcase";
|
|
19
19
|
import { AdobeCommerceHttpClient, resolveCommerceHttpClientParams, resolveIoEventsHttpClientParams } from "@adobe/aio-commerce-lib-api";
|
|
20
20
|
import { resolveAuthParams, resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";
|
|
21
|
-
import { createCustomCommerceEventsApiClient, createEventProvider, createEventSubscription, getAllEventProviders, getAllEventSubscriptions, updateEventingConfiguration } from "@adobe/aio-commerce-lib-events/commerce";
|
|
22
|
-
import { createCustomAdobeIoEventsApiClient, createEventMetadataForProvider, createEventProvider as createEventProvider$1, createRegistration, getAllEventProviders as getAllEventProviders$1, getAllRegistrations } from "@adobe/aio-commerce-lib-events/io-events";
|
|
23
|
-
import { createCustomCommerceWebhooksApiClient, getWebhookList, subscribeWebhook } from "@adobe/aio-commerce-lib-webhooks/api";
|
|
21
|
+
import { createCustomCommerceEventsApiClient, createEventProvider, createEventSubscription, deleteEventProvider, deleteEventSubscription, getAllEventProviders, getAllEventSubscriptions, updateEventingConfiguration } from "@adobe/aio-commerce-lib-events/commerce";
|
|
22
|
+
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
|
+
import { createCustomCommerceWebhooksApiClient, getWebhookList, subscribeWebhook, unsubscribeWebhook } from "@adobe/aio-commerce-lib-webhooks/api";
|
|
24
24
|
import { HTTPError } from "ky";
|
|
25
25
|
|
|
26
26
|
//#region source/management/installation/workflow/hooks.ts
|
|
@@ -47,8 +47,8 @@ function isBranchStep(step) {
|
|
|
47
47
|
* ```typescript
|
|
48
48
|
* const createProviders = defineLeafStep({
|
|
49
49
|
* name: "providers",
|
|
50
|
-
* meta: { label: "Create Providers", description: "Creates I/O Events providers" },
|
|
51
|
-
*
|
|
50
|
+
* meta: { install: { label: "Create Providers", description: "Creates I/O Events providers" } },
|
|
51
|
+
* install: async ({ config, stepContext }) => {
|
|
52
52
|
* const { eventsClient } = stepContext;
|
|
53
53
|
* return eventsClient.createProvider(config.eventing);
|
|
54
54
|
* },
|
|
@@ -61,7 +61,8 @@ function defineLeafStep(options) {
|
|
|
61
61
|
name: options.name,
|
|
62
62
|
meta: options.meta,
|
|
63
63
|
when: options.when,
|
|
64
|
-
|
|
64
|
+
install: options.install,
|
|
65
|
+
uninstall: options.uninstall,
|
|
65
66
|
validate: options.validate
|
|
66
67
|
};
|
|
67
68
|
}
|
|
@@ -72,7 +73,7 @@ function defineLeafStep(options) {
|
|
|
72
73
|
* ```typescript
|
|
73
74
|
* const eventing = defineBranchStep({
|
|
74
75
|
* name: "eventing",
|
|
75
|
-
* meta: { label: "Eventing", description: "Sets up I/O Events" },
|
|
76
|
+
* meta: { install: { label: "Eventing", description: "Sets up I/O Events" } },
|
|
76
77
|
* when: hasEventing,
|
|
77
78
|
* context: async (ctx) => ({ eventsClient: await createEventsClient(ctx) }),
|
|
78
79
|
* children: [commerceEventsStep, externalEventsStep],
|
|
@@ -153,12 +154,12 @@ function createFailedState(base, error) {
|
|
|
153
154
|
* tree structure with all steps set to "pending".
|
|
154
155
|
*/
|
|
155
156
|
function createInitialState(options) {
|
|
156
|
-
const { rootStep, config } = options;
|
|
157
|
+
const { rootStep, config, mode } = options;
|
|
157
158
|
return {
|
|
158
159
|
id: crypto.randomUUID(),
|
|
159
160
|
startedAt: nowIsoString(),
|
|
160
161
|
status: "in-progress",
|
|
161
|
-
step: buildInitialStepStatus(rootStep, config, []),
|
|
162
|
+
step: buildInitialStepStatus(rootStep, config, [], mode),
|
|
162
163
|
data: null
|
|
163
164
|
};
|
|
164
165
|
}
|
|
@@ -166,6 +167,19 @@ function createInitialState(options) {
|
|
|
166
167
|
* Executes a workflow from an initial state. Returns the final state (never throws).
|
|
167
168
|
*/
|
|
168
169
|
async function executeWorkflow(options) {
|
|
170
|
+
return executeWorkflowWithMode(options, "install");
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Executes an uninstall workflow from an initial state. Returns the final state (never throws).
|
|
174
|
+
* Steps with an `uninstall` handler get it called; steps without are silently skipped.
|
|
175
|
+
*/
|
|
176
|
+
async function executeUninstallWorkflow(options) {
|
|
177
|
+
return executeWorkflowWithMode(options, "uninstall");
|
|
178
|
+
}
|
|
179
|
+
/**
|
|
180
|
+
* Internal implementation shared by executeWorkflow and executeUninstallWorkflow.
|
|
181
|
+
*/
|
|
182
|
+
async function executeWorkflowWithMode(options, mode) {
|
|
169
183
|
const { rootStep, installationContext, config, initialState, hooks } = options;
|
|
170
184
|
const step = structuredClone(initialState.step);
|
|
171
185
|
const context = {
|
|
@@ -176,7 +190,8 @@ async function executeWorkflow(options) {
|
|
|
176
190
|
step,
|
|
177
191
|
data: null,
|
|
178
192
|
error: null,
|
|
179
|
-
hooks
|
|
193
|
+
hooks,
|
|
194
|
+
mode
|
|
180
195
|
};
|
|
181
196
|
await callHook(hooks, "onInstallationStart", snapshot(context));
|
|
182
197
|
try {
|
|
@@ -205,18 +220,18 @@ async function executeWorkflow(options) {
|
|
|
205
220
|
* Builds initial step status from a step definition.
|
|
206
221
|
* Filters steps based on their `when` conditions.
|
|
207
222
|
*/
|
|
208
|
-
function buildInitialStepStatus(step, config, parentPath) {
|
|
223
|
+
function buildInitialStepStatus(step, config, parentPath, mode) {
|
|
209
224
|
const path = [...parentPath, step.name];
|
|
210
225
|
const children = [];
|
|
211
226
|
if (isBranchStep(step) && step.children.length > 0) for (const child of step.children) {
|
|
212
227
|
if (child.when && !child.when(config)) continue;
|
|
213
|
-
children.push(buildInitialStepStatus(child, config, path));
|
|
228
|
+
children.push(buildInitialStepStatus(child, config, path, mode));
|
|
214
229
|
}
|
|
215
230
|
return {
|
|
216
231
|
id: crypto.randomUUID(),
|
|
217
232
|
name: step.name,
|
|
218
233
|
path,
|
|
219
|
-
meta: step.meta,
|
|
234
|
+
meta: mode === "uninstall" && step.meta.uninstall ? step.meta.uninstall : step.meta.install,
|
|
220
235
|
status: "pending",
|
|
221
236
|
children
|
|
222
237
|
};
|
|
@@ -280,13 +295,17 @@ async function executeBranchStep(step, stepStatus, inherited, context) {
|
|
|
280
295
|
await executeStep(childStep, child, childContext, context);
|
|
281
296
|
}
|
|
282
297
|
}
|
|
283
|
-
/** Executes a leaf step and stores its result. */
|
|
298
|
+
/** Executes a leaf step and stores its result, or runs uninstall if in uninstall mode. */
|
|
284
299
|
async function executeLeafStep(step, stepStatus, inherited, context) {
|
|
285
300
|
const executionContext = {
|
|
286
301
|
...context.installationContext,
|
|
287
302
|
...inherited
|
|
288
303
|
};
|
|
289
|
-
|
|
304
|
+
if (context.mode === "uninstall") {
|
|
305
|
+
if (step.uninstall) await step.uninstall(context.config, executionContext);
|
|
306
|
+
return;
|
|
307
|
+
}
|
|
308
|
+
const result = await step.install(context.config, executionContext);
|
|
290
309
|
context.data ??= {};
|
|
291
310
|
setAtPath(context.data, stepStatus.path, result);
|
|
292
311
|
}
|
|
@@ -347,7 +366,7 @@ async function validateStep(step, config, context, parentPath) {
|
|
|
347
366
|
return {
|
|
348
367
|
name: step.name,
|
|
349
368
|
path,
|
|
350
|
-
meta: step.meta,
|
|
369
|
+
meta: step.meta.install,
|
|
351
370
|
issues,
|
|
352
371
|
children
|
|
353
372
|
};
|
|
@@ -411,6 +430,12 @@ function aggregateSummary(result) {
|
|
|
411
430
|
|
|
412
431
|
//#endregion
|
|
413
432
|
//#region source/management/installation/custom-installation/custom-scripts.ts
|
|
433
|
+
function isCustomInstallationStepDefinition(obj) {
|
|
434
|
+
return typeof obj === "object" && obj !== null && "install" in obj && typeof obj.install === "function";
|
|
435
|
+
}
|
|
436
|
+
function isCustomInstallationStepHandler(obj) {
|
|
437
|
+
return typeof obj === "function";
|
|
438
|
+
}
|
|
414
439
|
/**
|
|
415
440
|
* Creates a leaf step for executing a single custom installation script.
|
|
416
441
|
*/
|
|
@@ -418,26 +443,48 @@ function createCustomScriptStep(scriptConfig) {
|
|
|
418
443
|
const { script, name, description } = scriptConfig;
|
|
419
444
|
return defineLeafStep({
|
|
420
445
|
name: camelcase(name),
|
|
421
|
-
meta: {
|
|
446
|
+
meta: { install: {
|
|
422
447
|
label: name,
|
|
423
448
|
description
|
|
424
|
-
},
|
|
425
|
-
|
|
449
|
+
} },
|
|
450
|
+
install: async (config, context) => {
|
|
426
451
|
const { logger } = context;
|
|
427
452
|
const customScripts = context.customScripts || {};
|
|
428
453
|
logger.info(`Executing custom installation script: ${name}`);
|
|
429
454
|
logger.debug(`Script path: ${script}`);
|
|
430
455
|
const scriptModule = customScripts[script];
|
|
431
456
|
if (!scriptModule) throw new Error(`Script ${script} not found in customScripts context. Make sure the script is defined in the configuration and the action was generated with custom scripts support.`);
|
|
432
|
-
if (typeof scriptModule !== "object" || !("default" in scriptModule)) throw new Error(`Script ${script} must export a default function. Use defineCustomInstallationStep helper.`);
|
|
433
|
-
const
|
|
434
|
-
|
|
457
|
+
if (typeof scriptModule !== "object" || !("default" in scriptModule)) throw new Error(`Script ${script} must export a default function or object. Use defineCustomInstallationStep helper.`);
|
|
458
|
+
const defaultExport = scriptModule.default;
|
|
459
|
+
let runFunction = null;
|
|
460
|
+
if (isCustomInstallationStepHandler(defaultExport)) runFunction = defaultExport;
|
|
461
|
+
else if (isCustomInstallationStepDefinition(defaultExport)) runFunction = defaultExport.install;
|
|
462
|
+
if (runFunction === null) throw new Error(`Script ${script} default export must be a function or an object with an install method. Use defineCustomInstallationStep helper.`);
|
|
435
463
|
const scriptResult = await runFunction(config, context);
|
|
436
464
|
logger.info(`Successfully executed script: ${name}`);
|
|
437
465
|
return {
|
|
438
466
|
script,
|
|
439
467
|
data: scriptResult
|
|
440
468
|
};
|
|
469
|
+
},
|
|
470
|
+
uninstall: async (config, context) => {
|
|
471
|
+
const { logger } = context;
|
|
472
|
+
const customScripts = context.customScripts || {};
|
|
473
|
+
logger.debug(`Uninstalling custom script: ${name}`);
|
|
474
|
+
const scriptModule = customScripts[script];
|
|
475
|
+
if (!scriptModule) throw new Error(`Script ${script} not found in customScripts context. Make sure the script is defined in the configuration and the action was generated with custom scripts support.`);
|
|
476
|
+
const defaultExport = scriptModule.default;
|
|
477
|
+
if (!isCustomInstallationStepDefinition(defaultExport)) {
|
|
478
|
+
logger.debug(`Script ${script} does not export an uninstall function, skipping uninstall.`);
|
|
479
|
+
return;
|
|
480
|
+
}
|
|
481
|
+
const { uninstall } = defaultExport;
|
|
482
|
+
if (!uninstall) {
|
|
483
|
+
logger.debug(`Script ${script} does not export an uninstall function, skipping uninstall.`);
|
|
484
|
+
return;
|
|
485
|
+
}
|
|
486
|
+
await uninstall(config, context);
|
|
487
|
+
logger.info(`Successfully uninstalled script: ${name}`);
|
|
441
488
|
}
|
|
442
489
|
});
|
|
443
490
|
}
|
|
@@ -458,8 +505,14 @@ function createCustomScriptSteps(config) {
|
|
|
458
505
|
const customInstallationStepBase = defineBranchStep({
|
|
459
506
|
name: "customInstallationSteps",
|
|
460
507
|
meta: {
|
|
461
|
-
|
|
462
|
-
|
|
508
|
+
install: {
|
|
509
|
+
label: "Custom Installation Steps",
|
|
510
|
+
description: "Executes custom installation scripts defined in the application configuration"
|
|
511
|
+
},
|
|
512
|
+
uninstall: {
|
|
513
|
+
label: "Custom Uninstallation Steps",
|
|
514
|
+
description: "Executes custom uninstallation scripts defined in the application configuration"
|
|
515
|
+
}
|
|
463
516
|
},
|
|
464
517
|
when: hasCustomInstallationSteps,
|
|
465
518
|
children: []
|
|
@@ -481,32 +534,37 @@ function createCustomInstallationStep(config) {
|
|
|
481
534
|
* Define a custom installation step with type-safe parameters.
|
|
482
535
|
*
|
|
483
536
|
* This helper provides type safety and IDE autocompletion for custom installation scripts.
|
|
484
|
-
*
|
|
537
|
+
* Accepts either a plain function (install only) or an object with `install` and optional
|
|
538
|
+
* `uninstall` handlers.
|
|
485
539
|
*
|
|
486
|
-
* @
|
|
487
|
-
* @returns The same handler function (for use as default export)
|
|
488
|
-
*
|
|
489
|
-
* @example
|
|
540
|
+
* @example Plain function (install only):
|
|
490
541
|
* ```typescript
|
|
491
542
|
* import { defineCustomInstallationStep } from "@adobe/aio-commerce-lib-app/management";
|
|
492
543
|
*
|
|
493
544
|
* export default defineCustomInstallationStep(async (config, context) => {
|
|
494
545
|
* const { logger, params } = context;
|
|
495
|
-
*
|
|
496
546
|
* logger.info(`Setting up ${config.metadata.displayName}...`);
|
|
547
|
+
* return { status: "success" };
|
|
548
|
+
* });
|
|
549
|
+
* ```
|
|
497
550
|
*
|
|
498
|
-
*
|
|
499
|
-
*
|
|
551
|
+
* @example Object form with install and uninstall:
|
|
552
|
+
* ```typescript
|
|
553
|
+
* import { defineCustomInstallationStep } from "@adobe/aio-commerce-lib-app/management";
|
|
500
554
|
*
|
|
501
|
-
*
|
|
502
|
-
*
|
|
503
|
-
*
|
|
504
|
-
*
|
|
555
|
+
* export default defineCustomInstallationStep({
|
|
556
|
+
* install: async (config, context) => {
|
|
557
|
+
* context.logger.info(`Registering ${config.metadata.displayName}...`);
|
|
558
|
+
* return { status: "success" };
|
|
559
|
+
* },
|
|
560
|
+
* uninstall: async (config, context) => {
|
|
561
|
+
* context.logger.info(`Removing ${config.metadata.displayName}...`);
|
|
562
|
+
* },
|
|
505
563
|
* });
|
|
506
564
|
* ```
|
|
507
565
|
*/
|
|
508
|
-
function defineCustomInstallationStep(
|
|
509
|
-
return
|
|
566
|
+
function defineCustomInstallationStep(handlerOrDefinition) {
|
|
567
|
+
return handlerOrDefinition;
|
|
510
568
|
}
|
|
511
569
|
|
|
512
570
|
//#endregion
|
|
@@ -529,6 +587,24 @@ async function registerExtension(context) {
|
|
|
529
587
|
logger.info(`Admin UI SDK extension registered successfully: ${response.extensionId}`);
|
|
530
588
|
return response;
|
|
531
589
|
}
|
|
590
|
+
/**
|
|
591
|
+
* Unregisters the extension from Commerce via DELETE /V1/adminuisdk/extension/:workspace_name/:extension_name.
|
|
592
|
+
* Best-effort: errors are logged as warnings and do not stop the uninstall workflow.
|
|
593
|
+
*
|
|
594
|
+
* @param context - The execution context providing the Commerce HTTP client and logger.
|
|
595
|
+
*/
|
|
596
|
+
async function uninstallExtension(context) {
|
|
597
|
+
const { commerceClient, appData, logger } = context;
|
|
598
|
+
const extensionName = process.env.__OW_NAMESPACE;
|
|
599
|
+
const endpoint = `adminuisdk/extension/${appData.workspaceName}/${extensionName}`;
|
|
600
|
+
logger.info(`Unregistering Admin UI SDK extension "${extensionName}" from workspace "${appData.workspaceName}"...`);
|
|
601
|
+
try {
|
|
602
|
+
await commerceClient.delete(endpoint);
|
|
603
|
+
logger.info(`Admin UI SDK extension "${extensionName}" unregistered successfully.`);
|
|
604
|
+
} catch (error) {
|
|
605
|
+
logger.warn(`Failed to unregister Admin UI SDK extension "${extensionName}": ${stringifyError(error)}. Continuing uninstall.`);
|
|
606
|
+
}
|
|
607
|
+
}
|
|
532
608
|
|
|
533
609
|
//#endregion
|
|
534
610
|
//#region source/management/installation/admin-ui-sdk/utils.ts
|
|
@@ -548,17 +624,30 @@ const createAdminUiSdkStepContext = (installation) => {
|
|
|
548
624
|
const registerExtensionStep = defineLeafStep({
|
|
549
625
|
name: "register-extension",
|
|
550
626
|
meta: {
|
|
551
|
-
|
|
552
|
-
|
|
627
|
+
install: {
|
|
628
|
+
label: "Register Extension",
|
|
629
|
+
description: "Registers the Admin UI SDK extension in Adobe Commerce"
|
|
630
|
+
},
|
|
631
|
+
uninstall: {
|
|
632
|
+
label: "Unregister Extension",
|
|
633
|
+
description: "Removes the Admin UI SDK extension from Adobe Commerce"
|
|
634
|
+
}
|
|
553
635
|
},
|
|
554
|
-
|
|
636
|
+
install: (_, context) => registerExtension(context),
|
|
637
|
+
uninstall: (_, context) => uninstallExtension(context)
|
|
555
638
|
});
|
|
556
639
|
/** Branch step for setting up the Admin UI SDK extension registration. */
|
|
557
640
|
const adminUiSdkStep = defineBranchStep({
|
|
558
641
|
name: "admin-ui-sdk",
|
|
559
642
|
meta: {
|
|
560
|
-
|
|
561
|
-
|
|
643
|
+
install: {
|
|
644
|
+
label: "Admin UI SDK",
|
|
645
|
+
description: "Registers the extension with Adobe Commerce Admin UI SDK"
|
|
646
|
+
},
|
|
647
|
+
uninstall: {
|
|
648
|
+
label: "Admin UI SDK",
|
|
649
|
+
description: "Removes the extension from Adobe Commerce Admin UI SDK"
|
|
650
|
+
}
|
|
562
651
|
},
|
|
563
652
|
when: hasAdminUiSdk,
|
|
564
653
|
context: createAdminUiSdkStepContext,
|
|
@@ -589,6 +678,17 @@ function generateInstanceId(metadata, provider, workspaceId) {
|
|
|
589
678
|
return `${appId}-${provider.key ?? slugLabel}-${workspaceId}`.toLowerCase();
|
|
590
679
|
}
|
|
591
680
|
/**
|
|
681
|
+
* Old version of instanceId generator which can be not unique within the same ORG.
|
|
682
|
+
*
|
|
683
|
+
* @param metadata - The metadata of the application
|
|
684
|
+
* @param provider - The event provider for which to generate the instance ID
|
|
685
|
+
* @deprecated use {@link generateInstanceId} instead
|
|
686
|
+
*/
|
|
687
|
+
function generateInstanceIdDeprecated(metadata, provider) {
|
|
688
|
+
const slugLabel = provider.label.toLowerCase().replace(/\s+/g, "-");
|
|
689
|
+
return `${metadata.id}-${provider.key ?? slugLabel}`.toLowerCase();
|
|
690
|
+
}
|
|
691
|
+
/**
|
|
592
692
|
* Find an existing event provider by its instance ID.
|
|
593
693
|
* @param allProviders - The list of all existing event providers.
|
|
594
694
|
* @param instanceId - The instance ID to search for.
|
|
@@ -678,6 +778,37 @@ function findExistingSubscription(allSubscriptions, eventName) {
|
|
|
678
778
|
return allSubscriptions.get(eventName) ?? null;
|
|
679
779
|
}
|
|
680
780
|
/**
|
|
781
|
+
* Builds the payload to send to Commerce when configuring Eventing.
|
|
782
|
+
* Returns `null` when no update call is needed.
|
|
783
|
+
*
|
|
784
|
+
* @param initialParams - Initial Commerce Eventing configuration parameters.
|
|
785
|
+
* @param existingData - Existing Commerce Eventing state from the API.
|
|
786
|
+
*/
|
|
787
|
+
function getCommerceEventingConfigurationUpdateParams(initialParams, existingData) {
|
|
788
|
+
const { isDefaultProviderConfigured, isDefaultWorkspaceConfigurationEmpty } = existingData;
|
|
789
|
+
if (isDefaultProviderConfigured && !isDefaultWorkspaceConfigurationEmpty) return null;
|
|
790
|
+
const { workspace_configuration, ...configWithoutWorkspace } = initialParams;
|
|
791
|
+
let updateParams = { enabled: true };
|
|
792
|
+
if (isDefaultWorkspaceConfigurationEmpty) {
|
|
793
|
+
if (!workspace_configuration) throw new Error("Workspace configuration is required to enable Commerce Eventing when there is not an existing one.");
|
|
794
|
+
updateParams.workspace_configuration = workspace_configuration;
|
|
795
|
+
}
|
|
796
|
+
if (!isDefaultProviderConfigured) updateParams = {
|
|
797
|
+
...updateParams,
|
|
798
|
+
...configWithoutWorkspace
|
|
799
|
+
};
|
|
800
|
+
return updateParams;
|
|
801
|
+
}
|
|
802
|
+
/**
|
|
803
|
+
* Sanitizes a Commerce Eventing identifier.
|
|
804
|
+
* Preserves underscores, converts spaces to underscores, lowercases, and strips the rest.
|
|
805
|
+
*
|
|
806
|
+
* @param value - The raw identifier value to normalize.
|
|
807
|
+
*/
|
|
808
|
+
function sanitizeEventingIdentifier(value) {
|
|
809
|
+
return value.toLowerCase().replace(/\s+/g, "_").replace(/[^a-z0-9_]/g, "");
|
|
810
|
+
}
|
|
811
|
+
/**
|
|
681
812
|
* Creates a partially filled workspace configuration object based on the app credentials and parameters.
|
|
682
813
|
* This configuration is used when creating an event provider in Commerce.
|
|
683
814
|
*
|
|
@@ -917,37 +1048,19 @@ async function createOrGetIoEventRegistration(params, registrations) {
|
|
|
917
1048
|
}
|
|
918
1049
|
/**
|
|
919
1050
|
* Ensures Commerce Eventing is configured with the given configuration, updating it if it already exists.
|
|
920
|
-
* @param
|
|
921
|
-
* @param
|
|
922
|
-
* @param existingData
|
|
1051
|
+
* @param params - The parameters necessary to configure Commerce Eventing.
|
|
1052
|
+
* @param existingData - Existing Commerce Eventing data.
|
|
923
1053
|
*/
|
|
924
1054
|
async function configureCommerceEventing(params, existingData) {
|
|
925
1055
|
const { context, config } = params;
|
|
926
1056
|
const { commerceEventsClient, logger } = context;
|
|
927
|
-
const { isDefaultProviderConfigured, isDefaultWorkspaceConfigurationEmpty } = existingData;
|
|
928
1057
|
logger.info("Starting configuration of the Commerce Eventing Module");
|
|
929
|
-
|
|
1058
|
+
const updateParams = getCommerceEventingConfigurationUpdateParams(config, existingData);
|
|
1059
|
+
if (updateParams === null) {
|
|
930
1060
|
logger.info("Commerce Eventing Module is already configured, skipping configuration step.");
|
|
931
1061
|
return;
|
|
932
1062
|
}
|
|
933
|
-
|
|
934
|
-
let updateParams = { enabled: true };
|
|
935
|
-
if (isDefaultWorkspaceConfigurationEmpty) {
|
|
936
|
-
if (!workspace_configuration) {
|
|
937
|
-
const message = "Workspace configuration is required to enable Commerce Eventing when there is not an existing one.";
|
|
938
|
-
logger.error(message);
|
|
939
|
-
throw new Error(message);
|
|
940
|
-
}
|
|
941
|
-
updateParams.workspace_configuration = workspace_configuration;
|
|
942
|
-
logger.info("Adding workspace configuration to Commerce Eventing configuration as it has not been set up yet.");
|
|
943
|
-
}
|
|
944
|
-
if (!isDefaultProviderConfigured) {
|
|
945
|
-
logger.info("Default provider not configured, it will be created with the provided configuration.");
|
|
946
|
-
updateParams = {
|
|
947
|
-
...updateParams,
|
|
948
|
-
...configWithoutWorkspace
|
|
949
|
-
};
|
|
950
|
-
}
|
|
1063
|
+
logger.info(`Updating Commerce Eventing Module configuration with the following data: [${Object.keys(updateParams).join(", ")}]`);
|
|
951
1064
|
return commerceEventsClient.updateEventingConfiguration(updateParams).then((success) => {
|
|
952
1065
|
if (success) {
|
|
953
1066
|
logger.info("Commerce Eventing Module configured successfully.");
|
|
@@ -1099,10 +1212,6 @@ async function onboardCommerceEventing(params, existingData) {
|
|
|
1099
1212
|
const { events, provider, workspaceConfiguration } = ioData;
|
|
1100
1213
|
const instanceId = provider.instance_id;
|
|
1101
1214
|
const subscriptions = [];
|
|
1102
|
-
await configureCommerceEventing({
|
|
1103
|
-
context,
|
|
1104
|
-
config: { workspace_configuration: workspaceConfiguration }
|
|
1105
|
-
}, existingData);
|
|
1106
1215
|
const { workspace_configuration: _, ...commerceProviderData } = await createOrGetCommerceProvider({
|
|
1107
1216
|
context,
|
|
1108
1217
|
provider: {
|
|
@@ -1124,6 +1233,181 @@ async function onboardCommerceEventing(params, existingData) {
|
|
|
1124
1233
|
subscriptions
|
|
1125
1234
|
};
|
|
1126
1235
|
}
|
|
1236
|
+
/**
|
|
1237
|
+
* Deletes all I/O Events registrations for the given provider.
|
|
1238
|
+
* Registration names are reconstructed deterministically using the same logic as during installation.
|
|
1239
|
+
* Errors are caught and logged so that uninstall remains best-effort.
|
|
1240
|
+
*/
|
|
1241
|
+
async function deleteIoEventRegistrations(providerData, provider, events, registrations, context) {
|
|
1242
|
+
const { ioEventsClient, appData, logger, params: runtimeParams } = context;
|
|
1243
|
+
const appCredentials = {
|
|
1244
|
+
consumerOrgId: appData.consumerOrgId,
|
|
1245
|
+
projectId: appData.projectId,
|
|
1246
|
+
workspaceId: appData.workspaceId
|
|
1247
|
+
};
|
|
1248
|
+
const actionEventsMap = groupEventsByRuntimeActions(events);
|
|
1249
|
+
const registrationNames = new Set(Array.from(actionEventsMap.keys()).map((runtimeAction) => getRegistrationName(providerData, runtimeAction)));
|
|
1250
|
+
const providerRegistrations = registrations.filter((reg) => reg.client_id === runtimeParams.AIO_COMMERCE_AUTH_IMS_CLIENT_ID && registrationNames.has(reg.name));
|
|
1251
|
+
if (providerRegistrations.length === 0) {
|
|
1252
|
+
logger.info(`No I/O Events registrations found for provider "${provider.label}" (instance ID: "${providerData.instance_id}").`);
|
|
1253
|
+
return;
|
|
1254
|
+
}
|
|
1255
|
+
logger.info(`Deleting ${providerRegistrations.length} I/O Events registration(s) for provider "${provider.label}" (instance ID: "${providerData.instance_id}")...`);
|
|
1256
|
+
for (const registration of providerRegistrations) {
|
|
1257
|
+
logger.info(`Deleting registration "${registration.name}" (ID: ${registration.id})...`);
|
|
1258
|
+
try {
|
|
1259
|
+
await ioEventsClient.deleteRegistration({
|
|
1260
|
+
...appCredentials,
|
|
1261
|
+
registrationId: registration.registration_id
|
|
1262
|
+
});
|
|
1263
|
+
logger.info(`Deleted registration "${registration.name}" (ID: ${registration.id}).`);
|
|
1264
|
+
} catch (error) {
|
|
1265
|
+
logger.warn(`Failed to delete registration "${registration.name}" (ID: ${registration.id}): ${stringifyError(error)}. Continuing uninstall.`);
|
|
1266
|
+
}
|
|
1267
|
+
}
|
|
1268
|
+
}
|
|
1269
|
+
/**
|
|
1270
|
+
* Deletes all event metadata entries from the given I/O Events provider.
|
|
1271
|
+
* Errors are caught and logged so that uninstall remains best-effort.
|
|
1272
|
+
*/
|
|
1273
|
+
async function deleteIoEventMetadata(providerData, provider, context) {
|
|
1274
|
+
const { ioEventsClient, appData, logger } = context;
|
|
1275
|
+
const appCredentials = {
|
|
1276
|
+
consumerOrgId: appData.consumerOrgId,
|
|
1277
|
+
projectId: appData.projectId,
|
|
1278
|
+
workspaceId: appData.workspaceId
|
|
1279
|
+
};
|
|
1280
|
+
const eventMetadataList = providerData.metadata ?? [];
|
|
1281
|
+
if (eventMetadataList.length === 0) {
|
|
1282
|
+
logger.info(`No event metadata found for provider "${provider.label}" (ID: ${providerData.id}).`);
|
|
1283
|
+
return;
|
|
1284
|
+
}
|
|
1285
|
+
logger.info(`Deleting ${eventMetadataList.length} event metadata entry(s) for provider "${provider.label}" (ID: ${providerData.id})...`);
|
|
1286
|
+
for (const eventMetadata of eventMetadataList) {
|
|
1287
|
+
logger.info(`Deleting event metadata "${eventMetadata.event_code}" from provider "${providerData.id}"...`);
|
|
1288
|
+
try {
|
|
1289
|
+
await ioEventsClient.deleteEventMetadataForProvider({
|
|
1290
|
+
...appCredentials,
|
|
1291
|
+
providerId: providerData.id,
|
|
1292
|
+
eventCode: eventMetadata.event_code
|
|
1293
|
+
});
|
|
1294
|
+
logger.info(`Deleted event metadata "${eventMetadata.event_code}" from provider "${providerData.id}".`);
|
|
1295
|
+
} catch (error) {
|
|
1296
|
+
logger.warn(`Failed to delete event metadata "${eventMetadata.event_code}" from provider "${providerData.id}": ${stringifyError(error)}. Continuing uninstall.`);
|
|
1297
|
+
}
|
|
1298
|
+
}
|
|
1299
|
+
}
|
|
1300
|
+
/**
|
|
1301
|
+
* Deletes a single I/O Events provider.
|
|
1302
|
+
* Errors are caught and logged so that uninstall remains best-effort.
|
|
1303
|
+
*/
|
|
1304
|
+
async function deleteIoEventProvider(providerData, provider, context) {
|
|
1305
|
+
const { ioEventsClient, appData, logger } = context;
|
|
1306
|
+
const appCredentials = {
|
|
1307
|
+
consumerOrgId: appData.consumerOrgId,
|
|
1308
|
+
projectId: appData.projectId,
|
|
1309
|
+
workspaceId: appData.workspaceId
|
|
1310
|
+
};
|
|
1311
|
+
logger.info(`Deleting I/O Events provider "${provider.label}" (ID: ${providerData.id})...`);
|
|
1312
|
+
try {
|
|
1313
|
+
await ioEventsClient.deleteEventProvider({
|
|
1314
|
+
...appCredentials,
|
|
1315
|
+
providerId: providerData.id
|
|
1316
|
+
});
|
|
1317
|
+
logger.info(`Deleted I/O Events provider "${provider.label}" (ID: ${providerData.id}).`);
|
|
1318
|
+
} catch (error) {
|
|
1319
|
+
logger.warn(`Failed to delete I/O Events provider "${provider.label}" (ID: ${providerData.id}): ${stringifyError(error)}. Continuing uninstall.`);
|
|
1320
|
+
}
|
|
1321
|
+
}
|
|
1322
|
+
/**
|
|
1323
|
+
* Offboards a single event source from I/O Events by deleting, in order:
|
|
1324
|
+
* 1. All registrations that reference events from this provider.
|
|
1325
|
+
* 2. All event metadata entries on the provider.
|
|
1326
|
+
* 3. The provider itself.
|
|
1327
|
+
*
|
|
1328
|
+
* This is the reverse of {@link onboardIoEvents} and is called during uninstall.
|
|
1329
|
+
* All deletion errors are caught and logged so that uninstall remains best-effort.
|
|
1330
|
+
*
|
|
1331
|
+
* @param params - Configuration identifying the provider to offboard.
|
|
1332
|
+
* @param existingData - Current I/O Events data (providers and registrations).
|
|
1333
|
+
*/
|
|
1334
|
+
async function offboardIoEvents(params, existingData) {
|
|
1335
|
+
const { context, metadata, provider, events } = params;
|
|
1336
|
+
const { appData, logger } = context;
|
|
1337
|
+
const instanceId = generateInstanceId(metadata, provider, appData.workspaceId);
|
|
1338
|
+
const instanceIdOldVersion = generateInstanceIdDeprecated(metadata, provider);
|
|
1339
|
+
const providerData = existingData.providersWithMetadata.find((p) => p.instance_id === instanceId || p.instance_id === instanceIdOldVersion);
|
|
1340
|
+
if (!providerData) {
|
|
1341
|
+
logger.info(`No I/O Events provider found with instance ID "${instanceId}", skipping offboarding.`);
|
|
1342
|
+
return;
|
|
1343
|
+
}
|
|
1344
|
+
await deleteIoEventRegistrations(providerData, provider, events, existingData.registrations, context);
|
|
1345
|
+
await deleteIoEventMetadata(providerData, provider, context);
|
|
1346
|
+
await deleteIoEventProvider(providerData, provider, context);
|
|
1347
|
+
}
|
|
1348
|
+
/**
|
|
1349
|
+
* Deletes all Commerce event subscriptions for the given events.
|
|
1350
|
+
* Subscriptions are matched by their namespaced name, built the same way as during installation.
|
|
1351
|
+
* Errors are caught and logged so that uninstall remains best-effort.
|
|
1352
|
+
*/
|
|
1353
|
+
async function deleteCommerceEventSubscriptions(events, metadata, provider, existingSubscriptions, context) {
|
|
1354
|
+
const { commerceEventsClient, logger } = context;
|
|
1355
|
+
logger.info(`Unsubscribing Commerce event subscriptions for provider "${provider.label}"...`);
|
|
1356
|
+
for (const event of events) {
|
|
1357
|
+
const eventName = getNamespacedEvent(metadata, event.name);
|
|
1358
|
+
if (!existingSubscriptions.has(eventName)) {
|
|
1359
|
+
logger.info(`No Commerce subscription found for event "${event.name}" (namespaced: "${eventName}"), skipping.`);
|
|
1360
|
+
continue;
|
|
1361
|
+
}
|
|
1362
|
+
logger.info(`Unsubscribing Commerce event subscription for "${event.name}" (namespaced: "${eventName}")...`);
|
|
1363
|
+
try {
|
|
1364
|
+
await commerceEventsClient.deleteEventSubscription({ name: eventName });
|
|
1365
|
+
logger.info(`Unsubscribed Commerce event subscription for "${eventName}".`);
|
|
1366
|
+
} catch (error) {
|
|
1367
|
+
logger.warn(`Failed to unsubscribe Commerce event subscription for "${eventName}": ${stringifyError(error)}. Continuing uninstall.`);
|
|
1368
|
+
}
|
|
1369
|
+
}
|
|
1370
|
+
}
|
|
1371
|
+
/**
|
|
1372
|
+
* Deletes a single Commerce-side event provider.
|
|
1373
|
+
* The provider is matched by its deterministic `instance_id`. If not found, deletion is skipped.
|
|
1374
|
+
* Errors are caught and logged so that uninstall remains best-effort.
|
|
1375
|
+
*/
|
|
1376
|
+
async function deleteCommerceEventProvider(metadata, provider, existingProviders, context) {
|
|
1377
|
+
const { commerceEventsClient, appData, logger } = context;
|
|
1378
|
+
const instanceId = generateInstanceId(metadata, provider, appData.workspaceId);
|
|
1379
|
+
const instanceIdOldVersion = generateInstanceIdDeprecated(metadata, provider);
|
|
1380
|
+
const commerceProvider = existingProviders.find((p) => p.instance_id === instanceId || p.instance_id === instanceIdOldVersion);
|
|
1381
|
+
if (!commerceProvider) {
|
|
1382
|
+
logger.info(`No Commerce event provider found with instance ID "${instanceId}", skipping provider deletion.`);
|
|
1383
|
+
return;
|
|
1384
|
+
}
|
|
1385
|
+
logger.info(`Deleting Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id})...`);
|
|
1386
|
+
try {
|
|
1387
|
+
await commerceEventsClient.deleteEventProvider({ provider_id: commerceProvider.provider_id });
|
|
1388
|
+
logger.info(`Deleted Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id}).`);
|
|
1389
|
+
} catch (error) {
|
|
1390
|
+
logger.warn(`Failed to delete Commerce event provider "${provider.label}" (provider_id: ${commerceProvider.provider_id}): ${stringifyError(error)}. Continuing uninstall.`);
|
|
1391
|
+
}
|
|
1392
|
+
}
|
|
1393
|
+
/**
|
|
1394
|
+
* Offboards Commerce eventing for a single provider. Performs the following steps in order:
|
|
1395
|
+
* 1. Unsubscribes all event subscriptions that were created for the given provider.
|
|
1396
|
+
* 2. Deletes the Commerce-side event provider itself.
|
|
1397
|
+
*
|
|
1398
|
+
* Subscriptions are matched by their namespaced name, which is deterministic and built the
|
|
1399
|
+
* same way as during {@link onboardCommerceEventing}. The provider is matched by its
|
|
1400
|
+
* `instance_id`. Missing subscriptions or providers are silently skipped. All errors are
|
|
1401
|
+
* caught and logged so that uninstall remains best-effort.
|
|
1402
|
+
*
|
|
1403
|
+
* @param params - Configuration identifying the provider and its events to offboard.
|
|
1404
|
+
* @param existingData - Current Commerce eventing data (providers and subscriptions).
|
|
1405
|
+
*/
|
|
1406
|
+
async function offboardCommerceEventing(params, existingData) {
|
|
1407
|
+
const { context, metadata, provider, events } = params;
|
|
1408
|
+
await deleteCommerceEventSubscriptions(events, metadata, provider, existingData.subscriptions, context);
|
|
1409
|
+
await deleteCommerceEventProvider(metadata, provider, existingData.providers, context);
|
|
1410
|
+
}
|
|
1127
1411
|
|
|
1128
1412
|
//#endregion
|
|
1129
1413
|
//#region source/management/installation/events/commerce.ts
|
|
@@ -1131,56 +1415,105 @@ async function onboardCommerceEventing(params, existingData) {
|
|
|
1131
1415
|
const commerceEventsStep = defineLeafStep({
|
|
1132
1416
|
name: "commerce",
|
|
1133
1417
|
meta: {
|
|
1134
|
-
|
|
1135
|
-
|
|
1418
|
+
install: {
|
|
1419
|
+
label: "Configure Commerce Events",
|
|
1420
|
+
description: "Sets up I/O Events for Adobe Commerce event sources"
|
|
1421
|
+
},
|
|
1422
|
+
uninstall: {
|
|
1423
|
+
label: "Remove Commerce Events",
|
|
1424
|
+
description: "Removes I/O Events for Adobe Commerce event sources"
|
|
1425
|
+
}
|
|
1136
1426
|
},
|
|
1137
1427
|
when: hasCommerceEvents,
|
|
1138
|
-
|
|
1139
|
-
|
|
1140
|
-
logger.debug("Starting installation of Commerce Events with config:", config);
|
|
1141
|
-
const stepData = [];
|
|
1142
|
-
const workspaceConfiguration = JSON.stringify(makeWorkspaceConfig(context));
|
|
1143
|
-
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
1144
|
-
const commerceEventingExistingData = await getCommerceEventingExistingData(context);
|
|
1145
|
-
for (const { provider, events } of config.eventing.commerce) {
|
|
1146
|
-
const { providerData, eventsData } = await onboardIoEvents({
|
|
1147
|
-
context,
|
|
1148
|
-
metadata: config.metadata,
|
|
1149
|
-
provider,
|
|
1150
|
-
events,
|
|
1151
|
-
providerType: COMMERCE_PROVIDER_TYPE
|
|
1152
|
-
}, existingIoEventsData);
|
|
1153
|
-
const { commerceProvider, subscriptions } = await onboardCommerceEventing({
|
|
1154
|
-
context,
|
|
1155
|
-
metadata: config.metadata,
|
|
1156
|
-
provider,
|
|
1157
|
-
ioData: {
|
|
1158
|
-
provider: providerData,
|
|
1159
|
-
events: eventsData,
|
|
1160
|
-
workspaceConfiguration
|
|
1161
|
-
}
|
|
1162
|
-
}, commerceEventingExistingData);
|
|
1163
|
-
stepData.push({ provider: {
|
|
1164
|
-
config: provider,
|
|
1165
|
-
data: {
|
|
1166
|
-
ioEvents: providerData,
|
|
1167
|
-
commerce: commerceProvider,
|
|
1168
|
-
events: eventsData.map(({ config, data }, index) => {
|
|
1169
|
-
return {
|
|
1170
|
-
config,
|
|
1171
|
-
data: {
|
|
1172
|
-
...data,
|
|
1173
|
-
subscription: subscriptions[index]
|
|
1174
|
-
}
|
|
1175
|
-
};
|
|
1176
|
-
})
|
|
1177
|
-
}
|
|
1178
|
-
} });
|
|
1179
|
-
}
|
|
1180
|
-
logger.debug("Completed Commerce Events installation step.");
|
|
1181
|
-
return stepData;
|
|
1182
|
-
}
|
|
1428
|
+
install: createCommerceEvents,
|
|
1429
|
+
uninstall: removeCommerceEvents
|
|
1183
1430
|
});
|
|
1431
|
+
/**
|
|
1432
|
+
* Creates all needed entities for Eventing to work with Commerce and Adobe I/O Events.
|
|
1433
|
+
* @param config - The configuration of the app, with commerce events.
|
|
1434
|
+
* @param context - The execution context for the events installation.
|
|
1435
|
+
*/
|
|
1436
|
+
async function createCommerceEvents(config, context) {
|
|
1437
|
+
const { logger } = context;
|
|
1438
|
+
logger.debug("Starting installation of Commerce Events with config:", config);
|
|
1439
|
+
const stepData = [];
|
|
1440
|
+
const workspaceConfiguration = JSON.stringify(makeWorkspaceConfig(context));
|
|
1441
|
+
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
1442
|
+
const commerceEventingExistingData = await getCommerceEventingExistingData(context);
|
|
1443
|
+
for (let i = 0; i < config.eventing.commerce.length; i++) {
|
|
1444
|
+
const { provider, events } = config.eventing.commerce[i];
|
|
1445
|
+
const { providerData, eventsData } = await onboardIoEvents({
|
|
1446
|
+
context,
|
|
1447
|
+
metadata: config.metadata,
|
|
1448
|
+
provider,
|
|
1449
|
+
events,
|
|
1450
|
+
providerType: COMMERCE_PROVIDER_TYPE
|
|
1451
|
+
}, existingIoEventsData);
|
|
1452
|
+
if (i === 0) await configureCommerceEventing({
|
|
1453
|
+
context,
|
|
1454
|
+
config: {
|
|
1455
|
+
enabled: true,
|
|
1456
|
+
merchant_id: sanitizeEventingIdentifier(context.appData.orgName),
|
|
1457
|
+
environment_id: sanitizeEventingIdentifier(context.appData.projectName),
|
|
1458
|
+
instance_id: providerData.instance_id,
|
|
1459
|
+
workspace_configuration: workspaceConfiguration
|
|
1460
|
+
}
|
|
1461
|
+
}, commerceEventingExistingData);
|
|
1462
|
+
const { commerceProvider, subscriptions } = await onboardCommerceEventing({
|
|
1463
|
+
context,
|
|
1464
|
+
metadata: config.metadata,
|
|
1465
|
+
provider,
|
|
1466
|
+
ioData: {
|
|
1467
|
+
provider: providerData,
|
|
1468
|
+
events: eventsData,
|
|
1469
|
+
workspaceConfiguration
|
|
1470
|
+
}
|
|
1471
|
+
}, commerceEventingExistingData);
|
|
1472
|
+
stepData.push({ provider: {
|
|
1473
|
+
config: provider,
|
|
1474
|
+
data: {
|
|
1475
|
+
ioEvents: providerData,
|
|
1476
|
+
commerce: commerceProvider,
|
|
1477
|
+
events: eventsData.map(({ config, data }, index) => {
|
|
1478
|
+
return {
|
|
1479
|
+
config,
|
|
1480
|
+
data: {
|
|
1481
|
+
...data,
|
|
1482
|
+
subscription: subscriptions[index]
|
|
1483
|
+
}
|
|
1484
|
+
};
|
|
1485
|
+
})
|
|
1486
|
+
}
|
|
1487
|
+
} });
|
|
1488
|
+
}
|
|
1489
|
+
logger.debug("Completed Commerce Events installation step.");
|
|
1490
|
+
return stepData;
|
|
1491
|
+
}
|
|
1492
|
+
/**
|
|
1493
|
+
* Remove all created for Commerce eventing created durint installation
|
|
1494
|
+
* @param config - The configuration of the app, with commerce events.
|
|
1495
|
+
* @param context - The execution context for the events installation.
|
|
1496
|
+
*/
|
|
1497
|
+
async function removeCommerceEvents(config, context) {
|
|
1498
|
+
const { logger } = context;
|
|
1499
|
+
logger.debug("Starting uninstall of Commerce Events with config:", config);
|
|
1500
|
+
const [existingIoEventsData, commerceEventingExistingData] = await Promise.all([getIoEventsExistingData(context), getCommerceEventingExistingData(context)]);
|
|
1501
|
+
for (const { provider, events } of config.eventing.commerce) {
|
|
1502
|
+
await offboardCommerceEventing({
|
|
1503
|
+
context,
|
|
1504
|
+
metadata: config.metadata,
|
|
1505
|
+
provider,
|
|
1506
|
+
events
|
|
1507
|
+
}, commerceEventingExistingData);
|
|
1508
|
+
await offboardIoEvents({
|
|
1509
|
+
context,
|
|
1510
|
+
metadata: config.metadata,
|
|
1511
|
+
provider,
|
|
1512
|
+
events
|
|
1513
|
+
}, existingIoEventsData);
|
|
1514
|
+
}
|
|
1515
|
+
logger.debug("Completed Commerce Events uninstall step.");
|
|
1516
|
+
}
|
|
1184
1517
|
|
|
1185
1518
|
//#endregion
|
|
1186
1519
|
//#region source/management/installation/events/context.ts
|
|
@@ -1196,6 +1529,8 @@ function createCommerceEventsApiClient(params) {
|
|
|
1196
1529
|
createEventProvider,
|
|
1197
1530
|
getAllEventProviders,
|
|
1198
1531
|
createEventSubscription,
|
|
1532
|
+
deleteEventProvider,
|
|
1533
|
+
deleteEventSubscription,
|
|
1199
1534
|
getAllEventSubscriptions,
|
|
1200
1535
|
updateEventingConfiguration
|
|
1201
1536
|
});
|
|
@@ -1212,6 +1547,9 @@ function createIoEventsApiClient(params) {
|
|
|
1212
1547
|
createEventProvider: createEventProvider$1,
|
|
1213
1548
|
createEventMetadataForProvider,
|
|
1214
1549
|
createRegistration,
|
|
1550
|
+
deleteEventMetadataForProvider,
|
|
1551
|
+
deleteEventProvider: deleteEventProvider$1,
|
|
1552
|
+
deleteRegistration,
|
|
1215
1553
|
getAllEventProviders: getAllEventProviders$1,
|
|
1216
1554
|
getAllRegistrations
|
|
1217
1555
|
});
|
|
@@ -1239,38 +1577,68 @@ function createEventsStepContext(installation) {
|
|
|
1239
1577
|
const externalEventsStep = defineLeafStep({
|
|
1240
1578
|
name: "external",
|
|
1241
1579
|
meta: {
|
|
1242
|
-
|
|
1243
|
-
|
|
1580
|
+
install: {
|
|
1581
|
+
label: "Configure External Events",
|
|
1582
|
+
description: "Sets up I/O Events for external event sources"
|
|
1583
|
+
},
|
|
1584
|
+
uninstall: {
|
|
1585
|
+
label: "Remove External Events",
|
|
1586
|
+
description: "Removes I/O Events for external event sources"
|
|
1587
|
+
}
|
|
1244
1588
|
},
|
|
1245
1589
|
when: hasExternalEvents,
|
|
1246
|
-
|
|
1247
|
-
|
|
1248
|
-
|
|
1249
|
-
|
|
1250
|
-
|
|
1251
|
-
|
|
1252
|
-
|
|
1253
|
-
|
|
1254
|
-
|
|
1255
|
-
|
|
1256
|
-
|
|
1257
|
-
|
|
1258
|
-
|
|
1259
|
-
|
|
1260
|
-
|
|
1261
|
-
|
|
1262
|
-
|
|
1263
|
-
|
|
1264
|
-
|
|
1265
|
-
|
|
1266
|
-
|
|
1590
|
+
install: createExternalEvents,
|
|
1591
|
+
uninstall: removeExternalEvents
|
|
1592
|
+
});
|
|
1593
|
+
/**
|
|
1594
|
+
* Creates all needed entities for External Events to work with Adobe I/O Events.
|
|
1595
|
+
* @param config - The configuration of the app, with external events.
|
|
1596
|
+
* @param context - The execution context for the events installation.
|
|
1597
|
+
*/
|
|
1598
|
+
async function createExternalEvents(config, context) {
|
|
1599
|
+
const { logger } = context;
|
|
1600
|
+
logger.debug("Starting installation of External Events with config:", config);
|
|
1601
|
+
const stepData = [];
|
|
1602
|
+
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
1603
|
+
for (const { provider, events } of config.eventing.external) {
|
|
1604
|
+
const { providerData, eventsData } = await onboardIoEvents({
|
|
1605
|
+
context,
|
|
1606
|
+
metadata: config.metadata,
|
|
1607
|
+
provider,
|
|
1608
|
+
events,
|
|
1609
|
+
providerType: EXTERNAL_PROVIDER_TYPE
|
|
1610
|
+
}, existingIoEventsData);
|
|
1611
|
+
stepData.push({ provider: {
|
|
1612
|
+
config: provider,
|
|
1613
|
+
data: {
|
|
1614
|
+
ioEvents: providerData,
|
|
1615
|
+
events: {
|
|
1616
|
+
config: events,
|
|
1617
|
+
data: eventsData
|
|
1267
1618
|
}
|
|
1268
|
-
}
|
|
1269
|
-
}
|
|
1270
|
-
logger.debug("Completed External Events installation step.");
|
|
1271
|
-
return stepData;
|
|
1619
|
+
}
|
|
1620
|
+
} });
|
|
1272
1621
|
}
|
|
1273
|
-
|
|
1622
|
+
logger.debug("Completed External Events installation step.");
|
|
1623
|
+
return stepData;
|
|
1624
|
+
}
|
|
1625
|
+
/**
|
|
1626
|
+
* Removed all created entities for External Events during the installation
|
|
1627
|
+
* @param config - The configuration of the app, with external events.
|
|
1628
|
+
* @param context - The execution context for the events installation.
|
|
1629
|
+
*/
|
|
1630
|
+
async function removeExternalEvents(config, context) {
|
|
1631
|
+
const { logger } = context;
|
|
1632
|
+
logger.debug("Starting uninstall of External Events with config:", config);
|
|
1633
|
+
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
1634
|
+
for (const { provider, events } of config.eventing.external) await offboardIoEvents({
|
|
1635
|
+
context,
|
|
1636
|
+
metadata: config.metadata,
|
|
1637
|
+
provider,
|
|
1638
|
+
events
|
|
1639
|
+
}, existingIoEventsData);
|
|
1640
|
+
logger.debug("Completed External Events uninstall step.");
|
|
1641
|
+
}
|
|
1274
1642
|
|
|
1275
1643
|
//#endregion
|
|
1276
1644
|
//#region source/management/installation/events/branch.ts
|
|
@@ -1278,8 +1646,14 @@ const externalEventsStep = defineLeafStep({
|
|
|
1278
1646
|
const eventingStep = defineBranchStep({
|
|
1279
1647
|
name: "eventing",
|
|
1280
1648
|
meta: {
|
|
1281
|
-
|
|
1282
|
-
|
|
1649
|
+
install: {
|
|
1650
|
+
label: "Eventing",
|
|
1651
|
+
description: "Sets up the I/O Events and the Commerce events required by the application"
|
|
1652
|
+
},
|
|
1653
|
+
uninstall: {
|
|
1654
|
+
label: "Eventing",
|
|
1655
|
+
description: "Removes the I/O Events and Commerce events configured by the application"
|
|
1656
|
+
}
|
|
1283
1657
|
},
|
|
1284
1658
|
when: hasEventing,
|
|
1285
1659
|
context: createEventsStepContext,
|
|
@@ -1298,7 +1672,8 @@ function createCommerceWebhooksApiClient(params) {
|
|
|
1298
1672
|
commerceClientParams.fetchOptions.timeout = 1e3 * 60 * 2;
|
|
1299
1673
|
return createCustomCommerceWebhooksApiClient(commerceClientParams, {
|
|
1300
1674
|
getWebhookList,
|
|
1301
|
-
subscribeWebhook
|
|
1675
|
+
subscribeWebhook,
|
|
1676
|
+
unsubscribeWebhook
|
|
1302
1677
|
});
|
|
1303
1678
|
}
|
|
1304
1679
|
/** Creates the webhooks step context with a lazy-initialized API client. */
|
|
@@ -1396,11 +1771,49 @@ async function createWebhookSubscriptions(config, context) {
|
|
|
1396
1771
|
return { subscribedWebhooks };
|
|
1397
1772
|
}
|
|
1398
1773
|
/**
|
|
1774
|
+
* Unsubscribes each webhook from the app config in Adobe Commerce.
|
|
1775
|
+
* If a webhook is not found in the existing list, it is silently skipped (idempotent).
|
|
1776
|
+
*
|
|
1777
|
+
* @param config - The app config (must have a non-empty `webhooks` array).
|
|
1778
|
+
* @param context - The webhooks execution context (provides the Commerce API client and logger).
|
|
1779
|
+
*/
|
|
1780
|
+
async function deleteWebhookSubscriptions(config, context) {
|
|
1781
|
+
const { logger, commerceWebhooksClient } = context;
|
|
1782
|
+
logger.info(`Unsubscribing ${config.webhooks.length} webhook(s) from Commerce...`);
|
|
1783
|
+
const idPrefix = buildWebhookIdPrefix(config.metadata.id);
|
|
1784
|
+
const unsubscribedWebhooks = [];
|
|
1785
|
+
const existingWebhooks = await commerceWebhooksClient.getWebhookList();
|
|
1786
|
+
for (const entry of config.webhooks) {
|
|
1787
|
+
const { webhook } = entry;
|
|
1788
|
+
const resolvedBatch = `${idPrefix}${webhook.batch_name}`;
|
|
1789
|
+
const resolvedHook = `${idPrefix}${webhook.hook_name}`;
|
|
1790
|
+
const params = {
|
|
1791
|
+
webhook_method: webhook.webhook_method,
|
|
1792
|
+
webhook_type: webhook.webhook_type,
|
|
1793
|
+
batch_name: resolvedBatch,
|
|
1794
|
+
hook_name: resolvedHook
|
|
1795
|
+
};
|
|
1796
|
+
if (!isWebhookInList(existingWebhooks, params)) {
|
|
1797
|
+
logger.debug(`Webhook not found, skipping unsubscribe: ${getWebhookName(webhook)}`);
|
|
1798
|
+
continue;
|
|
1799
|
+
}
|
|
1800
|
+
try {
|
|
1801
|
+
await deleteWebhookSubscription(commerceWebhooksClient, webhook, params);
|
|
1802
|
+
logger.info(`Unsubscribed webhook: ${getWebhookName(webhook)}`);
|
|
1803
|
+
unsubscribedWebhooks.push(params);
|
|
1804
|
+
} catch (error) {
|
|
1805
|
+
logger.warn(`Failed to unsubscribe webhook "${getWebhookName(webhook)}": ${stringifyError(error)}. Continuing uninstall.`);
|
|
1806
|
+
}
|
|
1807
|
+
}
|
|
1808
|
+
logger.info(`Webhook unsubscriptions complete: ${unsubscribedWebhooks.length} unsubscribed.`);
|
|
1809
|
+
return { unsubscribedWebhooks };
|
|
1810
|
+
}
|
|
1811
|
+
/**
|
|
1399
1812
|
* Subscribes a single webhook to Commerce, skipping the API call if the webhook
|
|
1400
1813
|
* is already subscribed (matched by webhook_method, webhook_type, batch_name, hook_name).
|
|
1401
1814
|
*/
|
|
1402
1815
|
async function createOrGetWebhookSubscription(existingWebhooks, client, resolvedWebhook, logger) {
|
|
1403
|
-
if (
|
|
1816
|
+
if (isWebhookInList(existingWebhooks, resolvedWebhook)) {
|
|
1404
1817
|
logger.info(`Webhook already subscribed, skipping: ${getWebhookName(resolvedWebhook)}`);
|
|
1405
1818
|
return resolvedWebhook;
|
|
1406
1819
|
}
|
|
@@ -1409,6 +1822,22 @@ async function createOrGetWebhookSubscription(existingWebhooks, client, resolved
|
|
|
1409
1822
|
return subscribed;
|
|
1410
1823
|
}
|
|
1411
1824
|
/**
|
|
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.
|
|
1827
|
+
*/
|
|
1828
|
+
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;
|
|
1839
|
+
}
|
|
1840
|
+
/**
|
|
1412
1841
|
* Subscribes a single webhook to Commerce, enriching the error with the webhook name
|
|
1413
1842
|
* if the API responds with a string `message`.
|
|
1414
1843
|
*/
|
|
@@ -1417,16 +1846,18 @@ async function createWebhookSubscription(client, resolvedWebhook) {
|
|
|
1417
1846
|
await client.subscribeWebhook(resolvedWebhook);
|
|
1418
1847
|
return resolvedWebhook;
|
|
1419
1848
|
} catch (err) {
|
|
1420
|
-
|
|
1421
|
-
|
|
1422
|
-
|
|
1423
|
-
|
|
1424
|
-
|
|
1425
|
-
|
|
1426
|
-
|
|
1427
|
-
|
|
1428
|
-
|
|
1429
|
-
|
|
1849
|
+
return await rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "subscription");
|
|
1850
|
+
}
|
|
1851
|
+
}
|
|
1852
|
+
/**
|
|
1853
|
+
* Unsubscribes a single webhook from Commerce, enriching the error with the webhook name
|
|
1854
|
+
* if the API responds with a string `message`.
|
|
1855
|
+
*/
|
|
1856
|
+
async function deleteWebhookSubscription(client, resolvedWebhook, params) {
|
|
1857
|
+
try {
|
|
1858
|
+
await client.unsubscribeWebhook(params);
|
|
1859
|
+
} catch (err) {
|
|
1860
|
+
await rethrowWithWebhookName(err, getWebhookName(resolvedWebhook), "unsubscription");
|
|
1430
1861
|
}
|
|
1431
1862
|
}
|
|
1432
1863
|
/**
|
|
@@ -1447,14 +1878,14 @@ function resolveDeveloperConsoleOAuthCredentials(params) {
|
|
|
1447
1878
|
};
|
|
1448
1879
|
}
|
|
1449
1880
|
/**
|
|
1450
|
-
* Returns true when the
|
|
1451
|
-
* matched by the four-part identity: webhook_method, webhook_type, batch_name, hook_name.
|
|
1881
|
+
* Returns true when a webhook with the given four-part identity exists in the list.
|
|
1452
1882
|
*
|
|
1883
|
+
* The identity check uses: webhook_method, webhook_type, batch_name, hook_name.
|
|
1453
1884
|
* `webhook_method` is normalised before comparison to handle the case where Commerce strips the
|
|
1454
1885
|
* `.magento` segment from plugin webhook methods on storage
|
|
1455
1886
|
* (e.g. `plugin.magento.foo` and `plugin.foo` are treated as the same method).
|
|
1456
1887
|
*/
|
|
1457
|
-
function
|
|
1888
|
+
function isWebhookInList(existing, candidate) {
|
|
1458
1889
|
const normalizedCandidate = normalizeWebhookMethod(candidate.webhook_method);
|
|
1459
1890
|
return existing.some((w) => normalizeWebhookMethod(w.webhook_method) === normalizedCandidate && w.webhook_type === candidate.webhook_type && w.batch_name === candidate.batch_name && w.hook_name === candidate.hook_name);
|
|
1460
1891
|
}
|
|
@@ -1512,18 +1943,33 @@ function getWebhookName(webhook) {
|
|
|
1512
1943
|
const subscriptionsStep = defineLeafStep({
|
|
1513
1944
|
name: "subscriptions",
|
|
1514
1945
|
meta: {
|
|
1515
|
-
|
|
1516
|
-
|
|
1946
|
+
install: {
|
|
1947
|
+
label: "Create Subscriptions",
|
|
1948
|
+
description: "Creates webhook subscriptions in Adobe Commerce"
|
|
1949
|
+
},
|
|
1950
|
+
uninstall: {
|
|
1951
|
+
label: "Delete Subscriptions",
|
|
1952
|
+
description: "Deletes webhook subscriptions from Adobe Commerce"
|
|
1953
|
+
}
|
|
1517
1954
|
},
|
|
1518
1955
|
validate: (config, context) => validateWebhookConflicts(config, context),
|
|
1519
|
-
|
|
1956
|
+
install: (config, context) => createWebhookSubscriptions(config, context),
|
|
1957
|
+
uninstall: async (config, context) => {
|
|
1958
|
+
await deleteWebhookSubscriptions(config, context);
|
|
1959
|
+
}
|
|
1520
1960
|
});
|
|
1521
1961
|
/** Branch step for setting up Commerce webhooks. */
|
|
1522
1962
|
const webhooksStep = defineBranchStep({
|
|
1523
1963
|
name: "webhooks",
|
|
1524
1964
|
meta: {
|
|
1525
|
-
|
|
1526
|
-
|
|
1965
|
+
install: {
|
|
1966
|
+
label: "Webhooks",
|
|
1967
|
+
description: "Sets up Commerce webhooks"
|
|
1968
|
+
},
|
|
1969
|
+
uninstall: {
|
|
1970
|
+
label: "Webhooks",
|
|
1971
|
+
description: "Removes Commerce webhooks"
|
|
1972
|
+
}
|
|
1527
1973
|
},
|
|
1528
1974
|
when: hasWebhooks,
|
|
1529
1975
|
context: createWebhooksStepContext,
|
|
@@ -1549,10 +1995,23 @@ function createDefaultChildSteps(config) {
|
|
|
1549
1995
|
function createRootInstallationStep(config) {
|
|
1550
1996
|
return defineBranchStep({
|
|
1551
1997
|
name: "installation",
|
|
1552
|
-
meta: {
|
|
1998
|
+
meta: { install: {
|
|
1553
1999
|
label: "Installation",
|
|
1554
2000
|
description: "App installation workflow"
|
|
1555
|
-
},
|
|
2001
|
+
} },
|
|
2002
|
+
children: createDefaultChildSteps(config)
|
|
2003
|
+
});
|
|
2004
|
+
}
|
|
2005
|
+
/**
|
|
2006
|
+
* Creates a root uninstallation step with dynamic children based on the config.
|
|
2007
|
+
*/
|
|
2008
|
+
function createRootUninstallationStep(config) {
|
|
2009
|
+
return defineBranchStep({
|
|
2010
|
+
name: "uninstallation",
|
|
2011
|
+
meta: { install: {
|
|
2012
|
+
label: "Uninstallation",
|
|
2013
|
+
description: "App uninstallation workflow"
|
|
2014
|
+
} },
|
|
1556
2015
|
children: createDefaultChildSteps(config)
|
|
1557
2016
|
});
|
|
1558
2017
|
}
|
|
@@ -1585,6 +2044,30 @@ function runInstallation(options) {
|
|
|
1585
2044
|
});
|
|
1586
2045
|
}
|
|
1587
2046
|
/**
|
|
2047
|
+
* Creates an initial uninstallation state from the config and step definitions.
|
|
2048
|
+
*/
|
|
2049
|
+
function createInitialUninstallationState(options) {
|
|
2050
|
+
const { config } = options;
|
|
2051
|
+
return createInitialState({
|
|
2052
|
+
rootStep: createRootUninstallationStep(config),
|
|
2053
|
+
config,
|
|
2054
|
+
mode: "uninstall"
|
|
2055
|
+
});
|
|
2056
|
+
}
|
|
2057
|
+
/**
|
|
2058
|
+
* Runs the full uninstallation workflow. Returns the final state (never throws).
|
|
2059
|
+
*/
|
|
2060
|
+
function runUninstallation(options) {
|
|
2061
|
+
const { installationContext, config, initialState, hooks } = options;
|
|
2062
|
+
return executeUninstallWorkflow({
|
|
2063
|
+
rootStep: createRootUninstallationStep(config),
|
|
2064
|
+
installationContext,
|
|
2065
|
+
config,
|
|
2066
|
+
initialState,
|
|
2067
|
+
hooks
|
|
2068
|
+
});
|
|
2069
|
+
}
|
|
2070
|
+
/**
|
|
1588
2071
|
* Runs pre-installation validation over the full step tree.
|
|
1589
2072
|
*
|
|
1590
2073
|
* Traverses the same step hierarchy used during installation but only calls
|
|
@@ -1602,4 +2085,4 @@ function runValidation(options) {
|
|
|
1602
2085
|
}
|
|
1603
2086
|
|
|
1604
2087
|
//#endregion
|
|
1605
|
-
export {
|
|
2088
|
+
export { runValidation as a, isFailedState as c, runUninstallation as i, isInProgressState as l, createInitialUninstallationState as n, defineCustomInstallationStep as o, runInstallation as r, isCompletedState as s, createInitialInstallationState as t, isSucceededState as u };
|