@adobe/aio-commerce-lib-app 1.0.2 → 1.2.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 +38 -0
- package/dist/cjs/actions/app-config.cjs +18 -4
- package/dist/cjs/actions/app-config.d.cts +17 -3
- package/dist/cjs/actions/config.cjs +17 -3
- package/dist/cjs/actions/config.d.cts +16 -2
- package/dist/cjs/actions/installation.cjs +70 -16
- package/dist/cjs/actions/installation.d.cts +18 -4
- package/dist/cjs/actions/scope-tree.cjs +17 -3
- package/dist/cjs/actions/scope-tree.d.cts +17 -3
- package/dist/cjs/{app-DWX5-Hsf.d.cts → app-DcQMhW2N.d.cts} +159 -2
- package/dist/cjs/commands/index.cjs +41 -27
- package/dist/cjs/commands/index.d.cts +14 -0
- package/dist/cjs/config/index.cjs +27 -12
- package/dist/cjs/config/index.d.cts +614 -6
- package/dist/cjs/{parser-BPpg_9QB.cjs → config-BppBKCFj.cjs} +17 -3
- package/dist/cjs/error-Dn7ool6k.cjs +38 -0
- package/dist/{es/runner-BD-lItnK.d.mts → cjs/index-DZxladgt.d.cts} +122 -9
- package/dist/cjs/logging-4s36JTiN.cjs +39 -0
- package/dist/cjs/management/index.cjs +23 -8
- package/dist/cjs/management/index.d.cts +16 -2
- package/dist/cjs/{runner-NHMvoMO2.cjs → management-iLQubQ7K.cjs} +462 -48
- package/dist/cjs/{router-DCw7oEQ9.cjs → router-KeQRduO3.cjs} +15 -1
- package/dist/cjs/{schemas-CZ6c8Id9.cjs → schemas-nkIxa8sL.cjs} +34 -0
- package/dist/cjs/{validate-Btzn9ilZ.cjs → validate-CeUCT_7k.cjs} +36 -15
- package/dist/cjs/{installation-CLbceU9F.cjs → webhooks-CLtDxwMa.cjs} +138 -1
- package/dist/es/actions/app-config.d.mts +17 -3
- package/dist/es/actions/app-config.mjs +17 -3
- package/dist/es/actions/config.d.mts +16 -2
- package/dist/es/actions/config.mjs +17 -3
- package/dist/es/actions/installation.d.mts +18 -4
- package/dist/es/actions/installation.mjs +64 -10
- package/dist/es/actions/scope-tree.d.mts +17 -3
- package/dist/es/actions/scope-tree.mjs +17 -3
- package/dist/es/{app-BAiyvNo2.d.mts → app-DJr-mN9d.d.mts} +159 -2
- package/dist/es/commands/index.d.mts +14 -0
- package/dist/es/commands/index.mjs +21 -7
- package/dist/es/config/index.d.mts +614 -6
- package/dist/es/config/index.mjs +18 -4
- package/dist/es/{parser-CQZTVG6i.mjs → config-BohwKkQS.mjs} +16 -2
- package/dist/es/error-DHlYzkbb.mjs +32 -0
- package/dist/{cjs/runner-DemKouFJ.d.cts → es/index-BmYXe7kp.d.mts} +122 -9
- package/dist/es/logging-XIUXDK5T.mjs +32 -0
- package/dist/es/management/index.d.mts +16 -2
- package/dist/es/management/index.mjs +16 -2
- package/dist/es/{runner-vwAhjD5r.mjs → management-DSexEPTW.mjs} +451 -43
- package/dist/es/{router-CJ4VWoCt.mjs → router-BxaxEEu3.mjs} +14 -0
- package/dist/es/{schemas-B8yIv0_b.mjs → schemas-CVXHgUhv.mjs} +29 -1
- package/dist/es/{validate-DKnju9-R.mjs → validate-CqJdGzyZ.mjs} +27 -6
- package/dist/es/{installation-BTL9X7iv.mjs → webhooks-CYo-pqbR.mjs} +115 -2
- package/package.json +12 -8
- package/dist/cjs/error-DJ2UAPH2.cjs +0 -24
- package/dist/cjs/logging-IDRQG0as.cjs +0 -25
- package/dist/es/error-CMV3IjBz.mjs +0 -18
- package/dist/es/logging-CzmXDzxI.mjs +0 -18
|
@@ -1,11 +1,27 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
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 { a as hasCustomInstallationSteps, c as hasEventing, d as hasAdminUiSdk, l as hasExternalEvents, n as hasWebhooks, s as hasCommerceEvents } from "./webhooks-CYo-pqbR.mjs";
|
|
16
|
+
import { t as stringifyError } from "./error-DHlYzkbb.mjs";
|
|
17
|
+
import { t as inspect } from "./logging-XIUXDK5T.mjs";
|
|
4
18
|
import camelcase from "camelcase";
|
|
5
|
-
import {
|
|
6
|
-
import {
|
|
19
|
+
import { AdobeCommerceHttpClient, resolveCommerceHttpClientParams, resolveIoEventsHttpClientParams } from "@adobe/aio-commerce-lib-api";
|
|
20
|
+
import { resolveAuthParams, resolveImsAuthParams } from "@adobe/aio-commerce-lib-auth";
|
|
7
21
|
import { createCustomCommerceEventsApiClient, createEventProvider, createEventSubscription, getAllEventProviders, getAllEventSubscriptions, updateEventingConfiguration } from "@adobe/aio-commerce-lib-events/commerce";
|
|
8
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";
|
|
24
|
+
import { HTTPError } from "ky";
|
|
9
25
|
|
|
10
26
|
//#region source/management/installation/workflow/hooks.ts
|
|
11
27
|
/** Helper to call a hook if it exists. */
|
|
@@ -45,7 +61,8 @@ function defineLeafStep(options) {
|
|
|
45
61
|
name: options.name,
|
|
46
62
|
meta: options.meta,
|
|
47
63
|
when: options.when,
|
|
48
|
-
run: options.run
|
|
64
|
+
run: options.run,
|
|
65
|
+
validate: options.validate
|
|
49
66
|
};
|
|
50
67
|
}
|
|
51
68
|
/**
|
|
@@ -69,7 +86,8 @@ function defineBranchStep(options) {
|
|
|
69
86
|
meta: options.meta,
|
|
70
87
|
when: options.when,
|
|
71
88
|
context: options.context,
|
|
72
|
-
children: options.children
|
|
89
|
+
children: options.children,
|
|
90
|
+
validate: options.validate
|
|
73
91
|
};
|
|
74
92
|
}
|
|
75
93
|
|
|
@@ -293,6 +311,104 @@ function isCompletedState(state) {
|
|
|
293
311
|
return state.status === "succeeded" || state.status === "failed";
|
|
294
312
|
}
|
|
295
313
|
|
|
314
|
+
//#endregion
|
|
315
|
+
//#region source/management/installation/workflow/validation.ts
|
|
316
|
+
/**
|
|
317
|
+
* Runs validation over the full step tree, returning a structured result.
|
|
318
|
+
*
|
|
319
|
+
* - Respects `when` conditions (skips steps that don't apply to the config)
|
|
320
|
+
* - Calls each step's optional `validate` handler
|
|
321
|
+
* - Sets up branch context factories before validating children
|
|
322
|
+
* - Never throws; all errors from validate handlers are caught and reported as issues
|
|
323
|
+
*/
|
|
324
|
+
async function validateStepTree(options) {
|
|
325
|
+
const { rootStep, validationContext, config } = options;
|
|
326
|
+
const result = await validateStep(rootStep, config, validationContext, []);
|
|
327
|
+
const summary = aggregateSummary(result);
|
|
328
|
+
return {
|
|
329
|
+
valid: summary.errors === 0 && summary.warnings === 0,
|
|
330
|
+
result,
|
|
331
|
+
summary
|
|
332
|
+
};
|
|
333
|
+
}
|
|
334
|
+
/** Recursively validates a single step and its children. */
|
|
335
|
+
async function validateStep(step, config, context, parentPath) {
|
|
336
|
+
const path = [...parentPath, step.name];
|
|
337
|
+
const issues = await runStepValidation(step, config, context);
|
|
338
|
+
const children = [];
|
|
339
|
+
if (isBranchStep(step) && step.children.length > 0) {
|
|
340
|
+
const resolved = await resolveBranchContext(step, context);
|
|
341
|
+
issues.push(...resolved.issues);
|
|
342
|
+
for (const child of step.children) {
|
|
343
|
+
if (child.when && !child.when(config)) continue;
|
|
344
|
+
children.push(await validateStep(child, config, resolved.childContext, path));
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
return {
|
|
348
|
+
name: step.name,
|
|
349
|
+
path,
|
|
350
|
+
meta: step.meta,
|
|
351
|
+
issues,
|
|
352
|
+
children
|
|
353
|
+
};
|
|
354
|
+
}
|
|
355
|
+
/** Resolves the child context for a branch step, reporting errors as issues. */
|
|
356
|
+
async function resolveBranchContext(step, context) {
|
|
357
|
+
if (!step.context) return {
|
|
358
|
+
childContext: context,
|
|
359
|
+
issues: []
|
|
360
|
+
};
|
|
361
|
+
try {
|
|
362
|
+
const stepContext = await step.context(context);
|
|
363
|
+
return {
|
|
364
|
+
childContext: {
|
|
365
|
+
...context,
|
|
366
|
+
...stepContext
|
|
367
|
+
},
|
|
368
|
+
issues: []
|
|
369
|
+
};
|
|
370
|
+
} catch (err) {
|
|
371
|
+
return {
|
|
372
|
+
childContext: context,
|
|
373
|
+
issues: [{
|
|
374
|
+
code: "VALIDATION_CONTEXT_ERROR",
|
|
375
|
+
message: err instanceof Error ? err.message : String(err),
|
|
376
|
+
severity: "error"
|
|
377
|
+
}]
|
|
378
|
+
};
|
|
379
|
+
}
|
|
380
|
+
}
|
|
381
|
+
/** Runs a step's validate handler, catching any thrown errors as issues. */
|
|
382
|
+
async function runStepValidation(step, config, context) {
|
|
383
|
+
if (!step.validate) return [];
|
|
384
|
+
try {
|
|
385
|
+
return await step.validate(config, context);
|
|
386
|
+
} catch (err) {
|
|
387
|
+
return [{
|
|
388
|
+
code: "VALIDATION_HANDLER_ERROR",
|
|
389
|
+
message: err instanceof Error ? err.message : String(err),
|
|
390
|
+
severity: "error"
|
|
391
|
+
}];
|
|
392
|
+
}
|
|
393
|
+
}
|
|
394
|
+
/** Recursively aggregates issue counts across the full validation tree. */
|
|
395
|
+
function aggregateSummary(result) {
|
|
396
|
+
let errors = 0;
|
|
397
|
+
let warnings = 0;
|
|
398
|
+
for (const issue of result.issues) if (issue.severity === "error") errors++;
|
|
399
|
+
else if (issue.severity === "warning") warnings++;
|
|
400
|
+
for (const child of result.children) {
|
|
401
|
+
const childSummary = aggregateSummary(child);
|
|
402
|
+
errors += childSummary.errors;
|
|
403
|
+
warnings += childSummary.warnings;
|
|
404
|
+
}
|
|
405
|
+
return {
|
|
406
|
+
totalIssues: errors + warnings,
|
|
407
|
+
errors,
|
|
408
|
+
warnings
|
|
409
|
+
};
|
|
410
|
+
}
|
|
411
|
+
|
|
296
412
|
//#endregion
|
|
297
413
|
//#region source/management/installation/custom-installation/custom-scripts.ts
|
|
298
414
|
/**
|
|
@@ -393,6 +509,62 @@ function defineCustomInstallationStep(handler) {
|
|
|
393
509
|
return handler;
|
|
394
510
|
}
|
|
395
511
|
|
|
512
|
+
//#endregion
|
|
513
|
+
//#region source/management/installation/admin-ui-sdk/helpers.ts
|
|
514
|
+
/**
|
|
515
|
+
* Registers the extension with Commerce via POST /V1/adminuisdk/extension.
|
|
516
|
+
*
|
|
517
|
+
* @param context - The execution context providing the Commerce HTTP client and logger.
|
|
518
|
+
* @returns The response from the Commerce API.
|
|
519
|
+
*/
|
|
520
|
+
async function registerExtension(context) {
|
|
521
|
+
const { commerceClient, appData, logger } = context;
|
|
522
|
+
logger.info(`Registering Admin UI SDK extension: ${appData.projectName}`);
|
|
523
|
+
const response = await commerceClient.post("adminuisdk/extension", { json: { extension: {
|
|
524
|
+
extensionName: process.env.__OW_NAMESPACE,
|
|
525
|
+
extensionTitle: appData.projectTitle,
|
|
526
|
+
extensionUrl: `https://${process.env.__OW_NAMESPACE}.adobeio-static.net/index.html`,
|
|
527
|
+
extensionWorkspace: appData.workspaceName
|
|
528
|
+
} } }).json();
|
|
529
|
+
logger.info(`Admin UI SDK extension registered successfully: ${response.extensionId}`);
|
|
530
|
+
return response;
|
|
531
|
+
}
|
|
532
|
+
|
|
533
|
+
//#endregion
|
|
534
|
+
//#region source/management/installation/admin-ui-sdk/utils.ts
|
|
535
|
+
/** Creates the Admin UI SDK step context with a lazy-initialized Commerce HTTP client. */
|
|
536
|
+
const createAdminUiSdkStepContext = (installation) => {
|
|
537
|
+
const { params } = installation;
|
|
538
|
+
let commerceClient = null;
|
|
539
|
+
return { get commerceClient() {
|
|
540
|
+
if (commerceClient === null) commerceClient = new AdobeCommerceHttpClient(resolveCommerceHttpClientParams(params, { tryForwardAuthProvider: true }));
|
|
541
|
+
return commerceClient;
|
|
542
|
+
} };
|
|
543
|
+
};
|
|
544
|
+
|
|
545
|
+
//#endregion
|
|
546
|
+
//#region source/management/installation/admin-ui-sdk/branch.ts
|
|
547
|
+
/** Leaf step that calls POST /V1/adminuisdk/extension to register the extension. */
|
|
548
|
+
const registerExtensionStep = defineLeafStep({
|
|
549
|
+
name: "register-extension",
|
|
550
|
+
meta: {
|
|
551
|
+
label: "Register Extension",
|
|
552
|
+
description: "Registers the Admin UI SDK extension in Adobe Commerce"
|
|
553
|
+
},
|
|
554
|
+
run: (_, context) => registerExtension(context)
|
|
555
|
+
});
|
|
556
|
+
/** Branch step for setting up the Admin UI SDK extension registration. */
|
|
557
|
+
const adminUiSdkStep = defineBranchStep({
|
|
558
|
+
name: "admin-ui-sdk",
|
|
559
|
+
meta: {
|
|
560
|
+
label: "Admin UI SDK",
|
|
561
|
+
description: "Registers the extension with Adobe Commerce Admin UI SDK"
|
|
562
|
+
},
|
|
563
|
+
when: hasAdminUiSdk,
|
|
564
|
+
context: createAdminUiSdkStepContext,
|
|
565
|
+
children: [registerExtensionStep]
|
|
566
|
+
});
|
|
567
|
+
|
|
396
568
|
//#endregion
|
|
397
569
|
//#region source/management/installation/events/utils.ts
|
|
398
570
|
const COMMERCE_PROVIDER_TYPE = "dx_commerce_events";
|
|
@@ -401,14 +573,20 @@ const PROVIDER_TYPE_TO_LABEL = {
|
|
|
401
573
|
[COMMERCE_PROVIDER_TYPE]: "Commerce",
|
|
402
574
|
[EXTERNAL_PROVIDER_TYPE]: "External"
|
|
403
575
|
};
|
|
576
|
+
/** Max characters taken from `metadata.id` in the I/O Events provider `instance_id`. */
|
|
577
|
+
const METADATA_ID_MAX_LENGTH_FOR_INSTANCE_ID = 100;
|
|
404
578
|
/**
|
|
405
|
-
* Generates a unique instance ID for
|
|
579
|
+
* Generates a unique instance ID for I/O Events for this app deployment.
|
|
580
|
+
* Uses `{metadata.id (first 100 chars)}-{providerKeyOrSlug}-{workspaceId}` (lowercased).
|
|
581
|
+
*
|
|
406
582
|
* @param metadata - The metadata of the application
|
|
407
|
-
* @param provider - The event provider
|
|
583
|
+
* @param provider - The event provider (optional `key`, else label is slugified)
|
|
584
|
+
* @param workspaceId - Adobe I/O Developer Console workspace ID for this deployment
|
|
408
585
|
*/
|
|
409
|
-
function generateInstanceId(metadata, provider) {
|
|
586
|
+
function generateInstanceId(metadata, provider, workspaceId) {
|
|
587
|
+
const appId = metadata.id.slice(0, METADATA_ID_MAX_LENGTH_FOR_INSTANCE_ID);
|
|
410
588
|
const slugLabel = provider.label.toLowerCase().replace(/\s+/g, "-");
|
|
411
|
-
return `${
|
|
589
|
+
return `${appId}-${provider.key ?? slugLabel}-${workspaceId}`.toLowerCase();
|
|
412
590
|
}
|
|
413
591
|
/**
|
|
414
592
|
* Find an existing event provider by its instance ID.
|
|
@@ -427,7 +605,6 @@ function findExistingProviderMetadata(allMetadata, eventName) {
|
|
|
427
605
|
return allMetadata.find((meta) => meta.event_code === eventName) ?? null;
|
|
428
606
|
}
|
|
429
607
|
/**
|
|
430
|
-
<<<<<<< HEAD
|
|
431
608
|
* Find existing event registrations by client ID and name.
|
|
432
609
|
* @param allRegistrations - The list of all existing event registrations.
|
|
433
610
|
* @param clientId - The client ID of the workspace where the registration was created.
|
|
@@ -442,7 +619,7 @@ function findExistingRegistrations(allRegistrations, clientId, name) {
|
|
|
442
619
|
* @param name
|
|
443
620
|
*/
|
|
444
621
|
function getNamespacedEvent(metadata, name) {
|
|
445
|
-
return `${metadata.id}.${name}
|
|
622
|
+
return `${metadata.id}.${name}`.toLowerCase();
|
|
446
623
|
}
|
|
447
624
|
/**
|
|
448
625
|
* Get the fully qualified name of an event for I/O Events based on the provider type.
|
|
@@ -450,7 +627,7 @@ function getNamespacedEvent(metadata, name) {
|
|
|
450
627
|
* @param providerType - The type of the event provider.
|
|
451
628
|
*/
|
|
452
629
|
function getIoEventCode(name, providerType) {
|
|
453
|
-
return providerType ===
|
|
630
|
+
return providerType === "dx_commerce_events" ? `com.adobe.commerce.${name}` : name;
|
|
454
631
|
}
|
|
455
632
|
/**
|
|
456
633
|
* Generates a registration name and description based on the provider, events, and runtime action.
|
|
@@ -458,7 +635,7 @@ function getIoEventCode(name, providerType) {
|
|
|
458
635
|
* @param runtimeAction - The runtime action this registration points to.
|
|
459
636
|
*/
|
|
460
637
|
function getRegistrationName(provider, runtimeAction) {
|
|
461
|
-
const providerLabel = PROVIDER_TYPE_TO_LABEL[provider.provider_metadata]
|
|
638
|
+
const providerLabel = PROVIDER_TYPE_TO_LABEL[provider.provider_metadata];
|
|
462
639
|
const [packageName, actionName] = runtimeAction.split("/").map(kebabToTitleCase);
|
|
463
640
|
return `${providerLabel} Event Registration: ${actionName} (${packageName})`;
|
|
464
641
|
}
|
|
@@ -582,8 +759,11 @@ async function getCommerceEventingExistingData(context) {
|
|
|
582
759
|
const { commerceEventsClient } = context;
|
|
583
760
|
const existingProviders = await commerceEventsClient.getAllEventProviders();
|
|
584
761
|
const existingSubscriptions = await commerceEventsClient.getAllEventSubscriptions();
|
|
762
|
+
const defaultProvider = existingProviders.find((provider) => !("id" in provider)) ?? null;
|
|
763
|
+
const isDefaultProviderConfigured = defaultProvider !== null;
|
|
585
764
|
return {
|
|
586
|
-
|
|
765
|
+
isDefaultProviderConfigured,
|
|
766
|
+
isDefaultWorkspaceConfigurationEmpty: isDefaultProviderConfigured ? !defaultProvider.workspace_configuration?.trim() : true,
|
|
587
767
|
providers: existingProviders,
|
|
588
768
|
subscriptions: new Map(existingSubscriptions.map((subscription) => [subscription.name, subscription]))
|
|
589
769
|
};
|
|
@@ -744,22 +924,30 @@ async function createOrGetIoEventRegistration(params, registrations) {
|
|
|
744
924
|
async function configureCommerceEventing(params, existingData) {
|
|
745
925
|
const { context, config } = params;
|
|
746
926
|
const { commerceEventsClient, logger } = context;
|
|
927
|
+
const { isDefaultProviderConfigured, isDefaultWorkspaceConfigurationEmpty } = existingData;
|
|
747
928
|
logger.info("Starting configuration of the Commerce Eventing Module");
|
|
748
|
-
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
}
|
|
752
|
-
|
|
753
|
-
|
|
929
|
+
if (isDefaultProviderConfigured && !isDefaultWorkspaceConfigurationEmpty) {
|
|
930
|
+
logger.info("Commerce Eventing Module is already configured, skipping configuration step.");
|
|
931
|
+
return;
|
|
932
|
+
}
|
|
933
|
+
const { workspace_configuration, ...configWithoutWorkspace } = config;
|
|
934
|
+
let updateParams = { enabled: true };
|
|
935
|
+
if (isDefaultWorkspaceConfigurationEmpty) {
|
|
936
|
+
if (!workspace_configuration) {
|
|
754
937
|
const message = "Workspace configuration is required to enable Commerce Eventing when there is not an existing one.";
|
|
755
938
|
logger.error(message);
|
|
756
939
|
throw new Error(message);
|
|
757
940
|
}
|
|
758
|
-
|
|
759
|
-
|
|
760
|
-
|
|
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
|
+
};
|
|
761
950
|
}
|
|
762
|
-
logger.info("Updating Commerce Eventing configuration with provided workspace configuration.");
|
|
763
951
|
return commerceEventsClient.updateEventingConfiguration(updateParams).then((success) => {
|
|
764
952
|
if (success) {
|
|
765
953
|
logger.info("Commerce Eventing Module configured successfully.");
|
|
@@ -858,7 +1046,7 @@ async function createOrGetCommerceEventSubscription(params, existingData) {
|
|
|
858
1046
|
async function onboardIoEvents(params, existingData) {
|
|
859
1047
|
const { providersWithMetadata, registrations } = existingData;
|
|
860
1048
|
const { context, metadata, provider, providerType, events } = params;
|
|
861
|
-
const instanceId = generateInstanceId(metadata, provider);
|
|
1049
|
+
const instanceId = generateInstanceId(metadata, provider, context.appData.workspaceId);
|
|
862
1050
|
const providerData = await createOrGetIoEventProvider({
|
|
863
1051
|
context,
|
|
864
1052
|
provider: {
|
|
@@ -1099,19 +1287,224 @@ const eventingStep = defineBranchStep({
|
|
|
1099
1287
|
});
|
|
1100
1288
|
|
|
1101
1289
|
//#endregion
|
|
1102
|
-
//#region source/management/installation/webhooks/
|
|
1103
|
-
|
|
1104
|
-
|
|
1105
|
-
|
|
1106
|
-
|
|
1290
|
+
//#region source/management/installation/webhooks/context.ts
|
|
1291
|
+
/**
|
|
1292
|
+
* Create a custom Commerce Webhooks API Client with only the operations needed for installation.
|
|
1293
|
+
* @param params - The runtime action params to resolve the client params from.
|
|
1294
|
+
*/
|
|
1295
|
+
function createCommerceWebhooksApiClient(params) {
|
|
1296
|
+
const commerceClientParams = resolveCommerceHttpClientParams(params, { tryForwardAuthProvider: true });
|
|
1297
|
+
commerceClientParams.fetchOptions ??= {};
|
|
1298
|
+
commerceClientParams.fetchOptions.timeout = 1e3 * 60 * 2;
|
|
1299
|
+
return createCustomCommerceWebhooksApiClient(commerceClientParams, {
|
|
1300
|
+
getWebhookList,
|
|
1301
|
+
subscribeWebhook
|
|
1302
|
+
});
|
|
1303
|
+
}
|
|
1304
|
+
/** Creates the webhooks step context with a lazy-initialized API client. */
|
|
1305
|
+
function createWebhooksStepContext(installation) {
|
|
1306
|
+
const { params } = installation;
|
|
1307
|
+
let commerceWebhooksClient = null;
|
|
1308
|
+
return { get commerceWebhooksClient() {
|
|
1309
|
+
if (commerceWebhooksClient === null) commerceWebhooksClient = createCommerceWebhooksApiClient(params);
|
|
1310
|
+
return commerceWebhooksClient;
|
|
1311
|
+
} };
|
|
1107
1312
|
}
|
|
1108
1313
|
|
|
1109
1314
|
//#endregion
|
|
1110
|
-
//#region source/management/installation/webhooks/
|
|
1111
|
-
/**
|
|
1112
|
-
|
|
1113
|
-
|
|
1114
|
-
|
|
1315
|
+
//#region source/management/installation/webhooks/helpers.ts
|
|
1316
|
+
/** Matches any character that is not a valid identifier character (letter, digit, or underscore). */
|
|
1317
|
+
const NON_IDENTIFIER_CHAR_REGEX = /[^a-zA-Z0-9_]/g;
|
|
1318
|
+
/** Matches two or more consecutive underscores. */
|
|
1319
|
+
const MULTIPLE_UNDERSCORES_REGEX = /_+/g;
|
|
1320
|
+
/** Matches the `.magento` segment in plugin webhook method names (e.g. `plugin.magento.foo`). */
|
|
1321
|
+
const PLUGIN_MAGENTO_REGEX = /^plugin\.magento\./;
|
|
1322
|
+
const ENVIRONMENT_PRODUCTION = "production";
|
|
1323
|
+
const ENVIRONMENT_STAGING = "staging";
|
|
1324
|
+
/**
|
|
1325
|
+
* Validates that no modification webhooks in the app config conflict with webhooks
|
|
1326
|
+
* already registered in Commerce by another app.
|
|
1327
|
+
*
|
|
1328
|
+
* A conflict is: Commerce has a webhook with the same `webhook_method` and `webhook_type`
|
|
1329
|
+
* that does NOT belong to this app (i.e. different `batch_name` or `hook_name` after prefix).
|
|
1330
|
+
*
|
|
1331
|
+
* Returns a `ValidationIssue` with code `WEBHOOK_CONFLICTS` and `details.conflictedWebhooks` listing
|
|
1332
|
+
* every conflicting Commerce webhook when conflicts are found, or an empty array otherwise.
|
|
1333
|
+
*
|
|
1334
|
+
* @param config - The app config (must have a non-empty `webhooks` array).
|
|
1335
|
+
* @param context - The webhooks execution context (provides the Commerce API client and logger).
|
|
1336
|
+
*/
|
|
1337
|
+
async function validateWebhookConflicts(config, context) {
|
|
1338
|
+
const { logger, commerceWebhooksClient } = context;
|
|
1339
|
+
const modificationWebhooks = config.webhooks.filter((entry) => entry.category === "modification");
|
|
1340
|
+
if (modificationWebhooks.length === 0) {
|
|
1341
|
+
logger.debug("No modification webhooks to validate, skipping conflict check.");
|
|
1342
|
+
return [];
|
|
1343
|
+
}
|
|
1344
|
+
logger.debug(`Validating ${modificationWebhooks.length} modification webhook(s) for conflicts...`);
|
|
1345
|
+
const existingWebhooks = await commerceWebhooksClient.getWebhookList();
|
|
1346
|
+
const idPrefix = buildWebhookIdPrefix(config.metadata.id);
|
|
1347
|
+
const conflictedWebhooks = [];
|
|
1348
|
+
for (const entry of modificationWebhooks) {
|
|
1349
|
+
const { webhook } = entry;
|
|
1350
|
+
const resolvedBatch = `${idPrefix}${webhook.batch_name}`;
|
|
1351
|
+
const resolvedHook = `${idPrefix}${webhook.hook_name}`;
|
|
1352
|
+
for (const existing of existingWebhooks) if (existing.webhook_method === webhook.webhook_method && existing.webhook_type === webhook.webhook_type && !(existing.batch_name === resolvedBatch && existing.hook_name === resolvedHook)) {
|
|
1353
|
+
conflictedWebhooks.push({
|
|
1354
|
+
label: entry.label,
|
|
1355
|
+
...existing
|
|
1356
|
+
});
|
|
1357
|
+
break;
|
|
1358
|
+
}
|
|
1359
|
+
}
|
|
1360
|
+
if (conflictedWebhooks.length > 0) return [{
|
|
1361
|
+
code: "WEBHOOK_CONFLICTS",
|
|
1362
|
+
message: `Webhook conflicts detected: ${conflictedWebhooks.length} webhook(s) already registered for the same method and type by another app`,
|
|
1363
|
+
severity: "warning",
|
|
1364
|
+
details: { conflictedWebhooks }
|
|
1365
|
+
}];
|
|
1366
|
+
logger.info("No webhook conflicts found.");
|
|
1367
|
+
return [];
|
|
1368
|
+
}
|
|
1369
|
+
/**
|
|
1370
|
+
* Subscribes each webhook from the app config to Adobe Commerce.
|
|
1371
|
+
* Throws on the first failure, aborting any remaining subscriptions.
|
|
1372
|
+
*
|
|
1373
|
+
* @param config - The app config (must have a non-empty `webhooks` array).
|
|
1374
|
+
* @param context - The webhooks execution context (provides the Commerce API client and logger).
|
|
1375
|
+
*/
|
|
1376
|
+
async function createWebhookSubscriptions(config, context) {
|
|
1377
|
+
const { logger, commerceWebhooksClient, params } = context;
|
|
1378
|
+
logger.info(`Subscribing ${config.webhooks.length} webhook(s) to Commerce...`);
|
|
1379
|
+
const idPrefix = buildWebhookIdPrefix(config.metadata.id);
|
|
1380
|
+
const subscribedWebhooks = [];
|
|
1381
|
+
const existingWebhooks = await commerceWebhooksClient.getWebhookList();
|
|
1382
|
+
for (const entry of config.webhooks) {
|
|
1383
|
+
const { webhook } = entry;
|
|
1384
|
+
const resolvedUrl = "runtimeAction" in entry ? generateUrlForRuntimeAction(entry.runtimeAction) : entry.webhook.url;
|
|
1385
|
+
logger.debug(`Subscribing webhook "${getWebhookName(webhook)}" (runtimeAction: ${"runtimeAction" in entry ? entry.runtimeAction : "none"})`);
|
|
1386
|
+
const resolvedWebhook = {
|
|
1387
|
+
...webhook,
|
|
1388
|
+
url: resolvedUrl,
|
|
1389
|
+
batch_name: `${idPrefix}${webhook.batch_name}`,
|
|
1390
|
+
hook_name: `${idPrefix}${webhook.hook_name}`,
|
|
1391
|
+
..."runtimeAction" in entry && entry.requireAdobeAuth !== false && { developer_console_oauth: resolveDeveloperConsoleOAuthCredentials(params) }
|
|
1392
|
+
};
|
|
1393
|
+
subscribedWebhooks.push(await createOrGetWebhookSubscription(existingWebhooks, commerceWebhooksClient, resolvedWebhook, logger));
|
|
1394
|
+
}
|
|
1395
|
+
logger.info(`Webhook subscriptions complete: ${subscribedWebhooks.length} subscribed.`);
|
|
1396
|
+
return { subscribedWebhooks };
|
|
1397
|
+
}
|
|
1398
|
+
/**
|
|
1399
|
+
* Subscribes a single webhook to Commerce, skipping the API call if the webhook
|
|
1400
|
+
* is already subscribed (matched by webhook_method, webhook_type, batch_name, hook_name).
|
|
1401
|
+
*/
|
|
1402
|
+
async function createOrGetWebhookSubscription(existingWebhooks, client, resolvedWebhook, logger) {
|
|
1403
|
+
if (isAlreadySubscribed(existingWebhooks, resolvedWebhook)) {
|
|
1404
|
+
logger.info(`Webhook already subscribed, skipping: ${getWebhookName(resolvedWebhook)}`);
|
|
1405
|
+
return resolvedWebhook;
|
|
1406
|
+
}
|
|
1407
|
+
const subscribed = await createWebhookSubscription(client, resolvedWebhook);
|
|
1408
|
+
logger.info(`Subscribed webhook: ${getWebhookName(resolvedWebhook)}`);
|
|
1409
|
+
return subscribed;
|
|
1410
|
+
}
|
|
1411
|
+
/**
|
|
1412
|
+
* Subscribes a single webhook to Commerce, enriching the error with the webhook name
|
|
1413
|
+
* if the API responds with a string `message`.
|
|
1414
|
+
*/
|
|
1415
|
+
async function createWebhookSubscription(client, resolvedWebhook) {
|
|
1416
|
+
try {
|
|
1417
|
+
await client.subscribeWebhook(resolvedWebhook);
|
|
1418
|
+
return resolvedWebhook;
|
|
1419
|
+
} catch (err) {
|
|
1420
|
+
if (err instanceof HTTPError) {
|
|
1421
|
+
let body;
|
|
1422
|
+
try {
|
|
1423
|
+
body = await err.response.json();
|
|
1424
|
+
} catch {
|
|
1425
|
+
throw err;
|
|
1426
|
+
}
|
|
1427
|
+
if (typeof body?.message === "string") throw new Error(`Webhook subscription failed for "${getWebhookName(resolvedWebhook)}": ${body.message}`);
|
|
1428
|
+
}
|
|
1429
|
+
throw err;
|
|
1430
|
+
}
|
|
1431
|
+
}
|
|
1432
|
+
/**
|
|
1433
|
+
* Resolves and validates the IMS credentials required for `developer_console_oauth`.
|
|
1434
|
+
*
|
|
1435
|
+
* Delegates parsing and validation to `resolveImsAuthParams` from `aio-commerce-lib-auth`,
|
|
1436
|
+
* which correctly handles `AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS` whether it arrives as a
|
|
1437
|
+
* real array or as a JSON-stringified array string.
|
|
1438
|
+
*/
|
|
1439
|
+
function resolveDeveloperConsoleOAuthCredentials(params) {
|
|
1440
|
+
const { AIO_COMMERCE_AUTH_IMS_ENVIRONMENT: imsEnvironment, ...imsParams } = params;
|
|
1441
|
+
const { clientId, clientSecrets, imsOrgId } = resolveImsAuthParams(imsParams);
|
|
1442
|
+
return {
|
|
1443
|
+
client_id: clientId,
|
|
1444
|
+
client_secret: clientSecrets[0],
|
|
1445
|
+
org_id: imsOrgId,
|
|
1446
|
+
environment: !imsEnvironment || String(imsEnvironment).startsWith("prod") ? ENVIRONMENT_PRODUCTION : ENVIRONMENT_STAGING
|
|
1447
|
+
};
|
|
1448
|
+
}
|
|
1449
|
+
/**
|
|
1450
|
+
* Returns true when the candidate webhook is already present in the existing subscription list,
|
|
1451
|
+
* matched by the four-part identity: webhook_method, webhook_type, batch_name, hook_name.
|
|
1452
|
+
*
|
|
1453
|
+
* `webhook_method` is normalised before comparison to handle the case where Commerce strips the
|
|
1454
|
+
* `.magento` segment from plugin webhook methods on storage
|
|
1455
|
+
* (e.g. `plugin.magento.foo` and `plugin.foo` are treated as the same method).
|
|
1456
|
+
*/
|
|
1457
|
+
function isAlreadySubscribed(existing, candidate) {
|
|
1458
|
+
const normalizedCandidate = normalizeWebhookMethod(candidate.webhook_method);
|
|
1459
|
+
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
|
+
}
|
|
1461
|
+
/**
|
|
1462
|
+
* Normalises a webhook method name by removing the `.magento` segment that Commerce
|
|
1463
|
+
* may drop when persisting plugin webhook methods.
|
|
1464
|
+
*
|
|
1465
|
+
* @example
|
|
1466
|
+
* normalizeWebhookMethod("plugin.magento.foo.bar") // → "plugin.foo.bar"
|
|
1467
|
+
* normalizeWebhookMethod("plugin.foo.bar") // → "plugin.foo.bar" (unchanged)
|
|
1468
|
+
*/
|
|
1469
|
+
function normalizeWebhookMethod(method) {
|
|
1470
|
+
return method.replace(PLUGIN_MAGENTO_REGEX, "plugin.");
|
|
1471
|
+
}
|
|
1472
|
+
/**
|
|
1473
|
+
* Generates a URL for a given runtime action using the AIO Runtime API host and namespace.
|
|
1474
|
+
* @param runtimeAction
|
|
1475
|
+
* @return The generated URL for the runtime action.
|
|
1476
|
+
*/
|
|
1477
|
+
function generateUrlForRuntimeAction(runtimeAction) {
|
|
1478
|
+
const namespace = process.env.__OW_NAMESPACE;
|
|
1479
|
+
if (!namespace) throw new Error(`Cannot generate URL for runtime action "${runtimeAction}": namespace environment variable is not set.`);
|
|
1480
|
+
return `https://${namespace}.adobeioruntime.net/api/v1/web/${runtimeAction}`;
|
|
1481
|
+
}
|
|
1482
|
+
/**
|
|
1483
|
+
* Builds a prefix string from the app ID to namespace webhook batch/hook names.
|
|
1484
|
+
* Non-identifier characters are replaced with underscores; consecutive underscores
|
|
1485
|
+
* are collapsed to one; a trailing underscore is appended. The result is lowercased
|
|
1486
|
+
* to ensure consistent matching regardless of input casing.
|
|
1487
|
+
*
|
|
1488
|
+
* @example
|
|
1489
|
+
* ```typescript
|
|
1490
|
+
* buildWebhookIdPrefix("my--app.v2") // => "my_app_v2_"
|
|
1491
|
+
* buildWebhookIdPrefix("MyApp") // => "myapp_"
|
|
1492
|
+
* ```
|
|
1493
|
+
* @param appId - The app ID to build the prefix from.
|
|
1494
|
+
* @return The built prefix string.
|
|
1495
|
+
*/
|
|
1496
|
+
function buildWebhookIdPrefix(appId) {
|
|
1497
|
+
const prefix = appId.toLowerCase().replace(NON_IDENTIFIER_CHAR_REGEX, "_").replace(MULTIPLE_UNDERSCORES_REGEX, "_");
|
|
1498
|
+
return prefix.endsWith("_") ? prefix : `${prefix}_`;
|
|
1499
|
+
}
|
|
1500
|
+
/**
|
|
1501
|
+
* Generates a name for a webhook based on its method and type.
|
|
1502
|
+
*
|
|
1503
|
+
* @param webhook
|
|
1504
|
+
* @return A string in the format "webhook_method:webhook_type" to identify the webhook.
|
|
1505
|
+
*/
|
|
1506
|
+
function getWebhookName(webhook) {
|
|
1507
|
+
return `${webhook.webhook_method}:${webhook.webhook_type}`;
|
|
1115
1508
|
}
|
|
1116
1509
|
|
|
1117
1510
|
//#endregion
|
|
@@ -1122,11 +1515,8 @@ const subscriptionsStep = defineLeafStep({
|
|
|
1122
1515
|
label: "Create Subscriptions",
|
|
1123
1516
|
description: "Creates webhook subscriptions in Adobe Commerce"
|
|
1124
1517
|
},
|
|
1125
|
-
|
|
1126
|
-
|
|
1127
|
-
logger.debug(config);
|
|
1128
|
-
return createWebhookSubscriptions(context);
|
|
1129
|
-
}
|
|
1518
|
+
validate: (config, context) => validateWebhookConflicts(config, context),
|
|
1519
|
+
run: (config, context) => createWebhookSubscriptions(config, context)
|
|
1130
1520
|
});
|
|
1131
1521
|
/** Branch step for setting up Commerce webhooks. */
|
|
1132
1522
|
const webhooksStep = defineBranchStep({
|
|
@@ -1136,6 +1526,7 @@ const webhooksStep = defineBranchStep({
|
|
|
1136
1526
|
description: "Sets up Commerce webhooks"
|
|
1137
1527
|
},
|
|
1138
1528
|
when: hasWebhooks,
|
|
1529
|
+
context: createWebhooksStepContext,
|
|
1139
1530
|
children: [subscriptionsStep]
|
|
1140
1531
|
});
|
|
1141
1532
|
|
|
@@ -1148,6 +1539,7 @@ function createDefaultChildSteps(config) {
|
|
|
1148
1539
|
return [
|
|
1149
1540
|
eventingStep,
|
|
1150
1541
|
webhooksStep,
|
|
1542
|
+
adminUiSdkStep,
|
|
1151
1543
|
createCustomInstallationStep(config)
|
|
1152
1544
|
];
|
|
1153
1545
|
}
|
|
@@ -1192,6 +1584,22 @@ function runInstallation(options) {
|
|
|
1192
1584
|
hooks
|
|
1193
1585
|
});
|
|
1194
1586
|
}
|
|
1587
|
+
/**
|
|
1588
|
+
* Runs pre-installation validation over the full step tree.
|
|
1589
|
+
*
|
|
1590
|
+
* Traverses the same step hierarchy used during installation but only calls
|
|
1591
|
+
* each step's optional `validate` handler rather than executing side effects.
|
|
1592
|
+
* Always resolves (never throws). Returns a structured result with per-step
|
|
1593
|
+
* issues and an aggregated summary.
|
|
1594
|
+
*/
|
|
1595
|
+
function runValidation(options) {
|
|
1596
|
+
const { validationContext, config } = options;
|
|
1597
|
+
return validateStepTree({
|
|
1598
|
+
rootStep: createRootInstallationStep(config),
|
|
1599
|
+
validationContext,
|
|
1600
|
+
config
|
|
1601
|
+
});
|
|
1602
|
+
}
|
|
1195
1603
|
|
|
1196
1604
|
//#endregion
|
|
1197
|
-
export {
|
|
1605
|
+
export { isCompletedState as a, isSucceededState as c, defineCustomInstallationStep as i, runInstallation as n, isFailedState as o, runValidation as r, isInProgressState as s, createInitialInstallationState as t };
|
|
@@ -1,3 +1,17 @@
|
|
|
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
|
+
|
|
1
15
|
import { badRequest, internalServerError, methodNotAllowed, notFound } from "@adobe/aio-commerce-lib-core/responses";
|
|
2
16
|
import AioLogger from "@adobe/aio-lib-core-logging";
|
|
3
17
|
import { parse } from "regexparam";
|