@adobe/aio-commerce-lib-app 1.0.2 → 1.1.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 +23 -0
- package/dist/cjs/actions/app-config.cjs +18 -4
- package/dist/cjs/actions/app-config.d.cts +15 -1
- package/dist/cjs/actions/config.cjs +17 -3
- package/dist/cjs/actions/config.d.cts +14 -0
- package/dist/cjs/actions/installation.cjs +70 -16
- package/dist/cjs/actions/installation.d.cts +16 -2
- package/dist/cjs/actions/scope-tree.cjs +17 -3
- package/dist/cjs/actions/scope-tree.d.cts +14 -0
- package/dist/cjs/{app-DWX5-Hsf.d.cts → app-lymFcs59.d.cts} +146 -1
- 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 +576 -4
- package/dist/cjs/{parser-BPpg_9QB.cjs → config-YEeaEqzi.cjs} +17 -3
- package/dist/cjs/error-Dn7ool6k.cjs +38 -0
- package/dist/{es/runner-BD-lItnK.d.mts → cjs/index-DRhLtRrX.d.cts} +102 -4
- 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-PZtLe4Ji.cjs} +371 -30
- 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-CwwYD8aC.cjs} +31 -14
- package/dist/cjs/{installation-CLbceU9F.cjs → webhooks-CbZpv9y_.cjs} +105 -1
- package/dist/es/actions/app-config.d.mts +15 -1
- package/dist/es/actions/app-config.mjs +17 -3
- package/dist/es/actions/config.d.mts +14 -0
- package/dist/es/actions/config.mjs +17 -3
- package/dist/es/actions/installation.d.mts +16 -2
- package/dist/es/actions/installation.mjs +64 -10
- package/dist/es/actions/scope-tree.d.mts +14 -0
- package/dist/es/actions/scope-tree.mjs +17 -3
- package/dist/es/{app-BAiyvNo2.d.mts → app-Ct7Y0NP8.d.mts} +146 -1
- package/dist/es/commands/index.d.mts +14 -0
- package/dist/es/commands/index.mjs +21 -7
- package/dist/es/config/index.d.mts +576 -4
- package/dist/es/config/index.mjs +18 -4
- package/dist/es/{parser-CQZTVG6i.mjs → config-BbrkH0Xt.mjs} +16 -2
- package/dist/es/error-DHlYzkbb.mjs +32 -0
- package/dist/{cjs/runner-DemKouFJ.d.cts → es/index-D33OCH0D.d.mts} +102 -4
- 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-CIoVWirU.mjs} +360 -25
- package/dist/es/{router-CJ4VWoCt.mjs → router-BxaxEEu3.mjs} +14 -0
- package/dist/es/{schemas-B8yIv0_b.mjs → schemas-BvPxQwgQ.mjs} +29 -1
- package/dist/es/{validate-DKnju9-R.mjs → validate-qRpfubPo.mjs} +21 -4
- package/dist/es/{installation-BTL9X7iv.mjs → webhooks-NgM6k3_r.mjs} +94 -2
- package/package.json +10 -7
- 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,13 +1,29 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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
|
+
const require_schemas = require('./schemas-nkIxa8sL.cjs');
|
|
16
|
+
const require_webhooks = require('./webhooks-CbZpv9y_.cjs');
|
|
17
|
+
const require_error = require('./error-Dn7ool6k.cjs');
|
|
18
|
+
const require_logging = require('./logging-4s36JTiN.cjs');
|
|
5
19
|
let camelcase = require("camelcase");
|
|
6
20
|
camelcase = require_schemas.__toESM(camelcase);
|
|
7
21
|
let _adobe_aio_commerce_lib_auth = require("@adobe/aio-commerce-lib-auth");
|
|
8
22
|
let _adobe_aio_commerce_lib_api = require("@adobe/aio-commerce-lib-api");
|
|
9
23
|
let _adobe_aio_commerce_lib_events_commerce = require("@adobe/aio-commerce-lib-events/commerce");
|
|
10
24
|
let _adobe_aio_commerce_lib_events_io_events = require("@adobe/aio-commerce-lib-events/io-events");
|
|
25
|
+
let _adobe_aio_commerce_lib_webhooks_api = require("@adobe/aio-commerce-lib-webhooks/api");
|
|
26
|
+
let ky = require("ky");
|
|
11
27
|
|
|
12
28
|
//#region source/management/installation/workflow/hooks.ts
|
|
13
29
|
/** Helper to call a hook if it exists. */
|
|
@@ -47,7 +63,8 @@ function defineLeafStep(options) {
|
|
|
47
63
|
name: options.name,
|
|
48
64
|
meta: options.meta,
|
|
49
65
|
when: options.when,
|
|
50
|
-
run: options.run
|
|
66
|
+
run: options.run,
|
|
67
|
+
validate: options.validate
|
|
51
68
|
};
|
|
52
69
|
}
|
|
53
70
|
/**
|
|
@@ -71,7 +88,8 @@ function defineBranchStep(options) {
|
|
|
71
88
|
meta: options.meta,
|
|
72
89
|
when: options.when,
|
|
73
90
|
context: options.context,
|
|
74
|
-
children: options.children
|
|
91
|
+
children: options.children,
|
|
92
|
+
validate: options.validate
|
|
75
93
|
};
|
|
76
94
|
}
|
|
77
95
|
|
|
@@ -295,6 +313,104 @@ function isCompletedState(state) {
|
|
|
295
313
|
return state.status === "succeeded" || state.status === "failed";
|
|
296
314
|
}
|
|
297
315
|
|
|
316
|
+
//#endregion
|
|
317
|
+
//#region source/management/installation/workflow/validation.ts
|
|
318
|
+
/**
|
|
319
|
+
* Runs validation over the full step tree, returning a structured result.
|
|
320
|
+
*
|
|
321
|
+
* - Respects `when` conditions (skips steps that don't apply to the config)
|
|
322
|
+
* - Calls each step's optional `validate` handler
|
|
323
|
+
* - Sets up branch context factories before validating children
|
|
324
|
+
* - Never throws; all errors from validate handlers are caught and reported as issues
|
|
325
|
+
*/
|
|
326
|
+
async function validateStepTree(options) {
|
|
327
|
+
const { rootStep, validationContext, config } = options;
|
|
328
|
+
const result = await validateStep(rootStep, config, validationContext, []);
|
|
329
|
+
const summary = aggregateSummary(result);
|
|
330
|
+
return {
|
|
331
|
+
valid: summary.errors === 0 && summary.warnings === 0,
|
|
332
|
+
result,
|
|
333
|
+
summary
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
/** Recursively validates a single step and its children. */
|
|
337
|
+
async function validateStep(step, config, context, parentPath) {
|
|
338
|
+
const path = [...parentPath, step.name];
|
|
339
|
+
const issues = await runStepValidation(step, config, context);
|
|
340
|
+
const children = [];
|
|
341
|
+
if (isBranchStep(step) && step.children.length > 0) {
|
|
342
|
+
const resolved = await resolveBranchContext(step, context);
|
|
343
|
+
issues.push(...resolved.issues);
|
|
344
|
+
for (const child of step.children) {
|
|
345
|
+
if (child.when && !child.when(config)) continue;
|
|
346
|
+
children.push(await validateStep(child, config, resolved.childContext, path));
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
return {
|
|
350
|
+
name: step.name,
|
|
351
|
+
path,
|
|
352
|
+
meta: step.meta,
|
|
353
|
+
issues,
|
|
354
|
+
children
|
|
355
|
+
};
|
|
356
|
+
}
|
|
357
|
+
/** Resolves the child context for a branch step, reporting errors as issues. */
|
|
358
|
+
async function resolveBranchContext(step, context) {
|
|
359
|
+
if (!step.context) return {
|
|
360
|
+
childContext: context,
|
|
361
|
+
issues: []
|
|
362
|
+
};
|
|
363
|
+
try {
|
|
364
|
+
const stepContext = await step.context(context);
|
|
365
|
+
return {
|
|
366
|
+
childContext: {
|
|
367
|
+
...context,
|
|
368
|
+
...stepContext
|
|
369
|
+
},
|
|
370
|
+
issues: []
|
|
371
|
+
};
|
|
372
|
+
} catch (err) {
|
|
373
|
+
return {
|
|
374
|
+
childContext: context,
|
|
375
|
+
issues: [{
|
|
376
|
+
code: "VALIDATION_CONTEXT_ERROR",
|
|
377
|
+
message: err instanceof Error ? err.message : String(err),
|
|
378
|
+
severity: "error"
|
|
379
|
+
}]
|
|
380
|
+
};
|
|
381
|
+
}
|
|
382
|
+
}
|
|
383
|
+
/** Runs a step's validate handler, catching any thrown errors as issues. */
|
|
384
|
+
async function runStepValidation(step, config, context) {
|
|
385
|
+
if (!step.validate) return [];
|
|
386
|
+
try {
|
|
387
|
+
return await step.validate(config, context);
|
|
388
|
+
} catch (err) {
|
|
389
|
+
return [{
|
|
390
|
+
code: "VALIDATION_HANDLER_ERROR",
|
|
391
|
+
message: err instanceof Error ? err.message : String(err),
|
|
392
|
+
severity: "error"
|
|
393
|
+
}];
|
|
394
|
+
}
|
|
395
|
+
}
|
|
396
|
+
/** Recursively aggregates issue counts across the full validation tree. */
|
|
397
|
+
function aggregateSummary(result) {
|
|
398
|
+
let errors = 0;
|
|
399
|
+
let warnings = 0;
|
|
400
|
+
for (const issue of result.issues) if (issue.severity === "error") errors++;
|
|
401
|
+
else if (issue.severity === "warning") warnings++;
|
|
402
|
+
for (const child of result.children) {
|
|
403
|
+
const childSummary = aggregateSummary(child);
|
|
404
|
+
errors += childSummary.errors;
|
|
405
|
+
warnings += childSummary.warnings;
|
|
406
|
+
}
|
|
407
|
+
return {
|
|
408
|
+
totalIssues: errors + warnings,
|
|
409
|
+
errors,
|
|
410
|
+
warnings
|
|
411
|
+
};
|
|
412
|
+
}
|
|
413
|
+
|
|
298
414
|
//#endregion
|
|
299
415
|
//#region source/management/installation/custom-installation/custom-scripts.ts
|
|
300
416
|
/**
|
|
@@ -332,7 +448,7 @@ function createCustomScriptStep(scriptConfig) {
|
|
|
332
448
|
* defined in the configuration. Each script becomes a separate leaf step.
|
|
333
449
|
*/
|
|
334
450
|
function createCustomScriptSteps(config) {
|
|
335
|
-
if (!
|
|
451
|
+
if (!require_webhooks.hasCustomInstallationSteps(config)) return [];
|
|
336
452
|
const steps = config.installation.customInstallationSteps;
|
|
337
453
|
if (new Set(steps.map((step) => step.name)).size !== steps.length) throw new Error("Duplicate step names detected in custom installation steps. Each step must have a unique name.");
|
|
338
454
|
return steps.map((scriptConfig) => createCustomScriptStep(scriptConfig));
|
|
@@ -347,7 +463,7 @@ const customInstallationStepBase = defineBranchStep({
|
|
|
347
463
|
label: "Custom Installation Steps",
|
|
348
464
|
description: "Executes custom installation scripts defined in the application configuration"
|
|
349
465
|
},
|
|
350
|
-
when:
|
|
466
|
+
when: require_webhooks.hasCustomInstallationSteps,
|
|
351
467
|
children: []
|
|
352
468
|
});
|
|
353
469
|
/**
|
|
@@ -410,7 +526,7 @@ const PROVIDER_TYPE_TO_LABEL = {
|
|
|
410
526
|
*/
|
|
411
527
|
function generateInstanceId(metadata, provider) {
|
|
412
528
|
const slugLabel = provider.label.toLowerCase().replace(/\s+/g, "-");
|
|
413
|
-
return `${metadata.id}-${provider.key ?? slugLabel}
|
|
529
|
+
return `${metadata.id}-${provider.key ?? slugLabel}`.toLowerCase();
|
|
414
530
|
}
|
|
415
531
|
/**
|
|
416
532
|
* Find an existing event provider by its instance ID.
|
|
@@ -444,7 +560,7 @@ function findExistingRegistrations(allRegistrations, clientId, name) {
|
|
|
444
560
|
* @param name
|
|
445
561
|
*/
|
|
446
562
|
function getNamespacedEvent(metadata, name) {
|
|
447
|
-
return `${metadata.id}.${name}
|
|
563
|
+
return `${metadata.id}.${name}`.toLowerCase();
|
|
448
564
|
}
|
|
449
565
|
/**
|
|
450
566
|
* Get the fully qualified name of an event for I/O Events based on the provider type.
|
|
@@ -452,7 +568,7 @@ function getNamespacedEvent(metadata, name) {
|
|
|
452
568
|
* @param providerType - The type of the event provider.
|
|
453
569
|
*/
|
|
454
570
|
function getIoEventCode(name, providerType) {
|
|
455
|
-
return providerType ===
|
|
571
|
+
return providerType === "dx_commerce_events" ? `com.adobe.commerce.${name}` : name;
|
|
456
572
|
}
|
|
457
573
|
/**
|
|
458
574
|
* Generates a registration name and description based on the provider, events, and runtime action.
|
|
@@ -948,7 +1064,7 @@ const commerceEventsStep = defineLeafStep({
|
|
|
948
1064
|
label: "Configure Commerce Events",
|
|
949
1065
|
description: "Sets up I/O Events for Adobe Commerce event sources"
|
|
950
1066
|
},
|
|
951
|
-
when:
|
|
1067
|
+
when: require_webhooks.hasCommerceEvents,
|
|
952
1068
|
run: async (config, context) => {
|
|
953
1069
|
const { logger } = context;
|
|
954
1070
|
logger.debug("Starting installation of Commerce Events with config:", config);
|
|
@@ -1056,7 +1172,7 @@ const externalEventsStep = defineLeafStep({
|
|
|
1056
1172
|
label: "Configure External Events",
|
|
1057
1173
|
description: "Sets up I/O Events for external event sources"
|
|
1058
1174
|
},
|
|
1059
|
-
when:
|
|
1175
|
+
when: require_webhooks.hasExternalEvents,
|
|
1060
1176
|
run: async (config, context) => {
|
|
1061
1177
|
const { logger } = context;
|
|
1062
1178
|
logger.debug("Starting installation of External Events with config:", config);
|
|
@@ -1095,25 +1211,230 @@ const eventingStep = defineBranchStep({
|
|
|
1095
1211
|
label: "Eventing",
|
|
1096
1212
|
description: "Sets up the I/O Events and the Commerce events required by the application"
|
|
1097
1213
|
},
|
|
1098
|
-
when:
|
|
1214
|
+
when: require_webhooks.hasEventing,
|
|
1099
1215
|
context: createEventsStepContext,
|
|
1100
1216
|
children: [commerceEventsStep, externalEventsStep]
|
|
1101
1217
|
});
|
|
1102
1218
|
|
|
1103
1219
|
//#endregion
|
|
1104
|
-
//#region source/management/installation/webhooks/
|
|
1105
|
-
|
|
1106
|
-
|
|
1107
|
-
|
|
1108
|
-
|
|
1220
|
+
//#region source/management/installation/webhooks/context.ts
|
|
1221
|
+
/**
|
|
1222
|
+
* Create a custom Commerce Webhooks API Client with only the operations needed for installation.
|
|
1223
|
+
* @param params - The runtime action params to resolve the client params from.
|
|
1224
|
+
*/
|
|
1225
|
+
function createCommerceWebhooksApiClient(params) {
|
|
1226
|
+
const commerceClientParams = (0, _adobe_aio_commerce_lib_api.resolveCommerceHttpClientParams)(params, { tryForwardAuthProvider: true });
|
|
1227
|
+
commerceClientParams.fetchOptions ??= {};
|
|
1228
|
+
commerceClientParams.fetchOptions.timeout = 1e3 * 60 * 2;
|
|
1229
|
+
return (0, _adobe_aio_commerce_lib_webhooks_api.createCustomCommerceWebhooksApiClient)(commerceClientParams, {
|
|
1230
|
+
getWebhookList: _adobe_aio_commerce_lib_webhooks_api.getWebhookList,
|
|
1231
|
+
subscribeWebhook: _adobe_aio_commerce_lib_webhooks_api.subscribeWebhook
|
|
1232
|
+
});
|
|
1233
|
+
}
|
|
1234
|
+
/** Creates the webhooks step context with a lazy-initialized API client. */
|
|
1235
|
+
function createWebhooksStepContext(installation) {
|
|
1236
|
+
const { params } = installation;
|
|
1237
|
+
let commerceWebhooksClient = null;
|
|
1238
|
+
return { get commerceWebhooksClient() {
|
|
1239
|
+
if (commerceWebhooksClient === null) commerceWebhooksClient = createCommerceWebhooksApiClient(params);
|
|
1240
|
+
return commerceWebhooksClient;
|
|
1241
|
+
} };
|
|
1109
1242
|
}
|
|
1110
1243
|
|
|
1111
1244
|
//#endregion
|
|
1112
|
-
//#region source/management/installation/webhooks/
|
|
1113
|
-
/**
|
|
1114
|
-
|
|
1115
|
-
|
|
1116
|
-
|
|
1245
|
+
//#region source/management/installation/webhooks/helpers.ts
|
|
1246
|
+
/** Matches any character that is not a valid identifier character (letter, digit, or underscore). */
|
|
1247
|
+
const NON_IDENTIFIER_CHAR_REGEX = /[^a-zA-Z0-9_]/g;
|
|
1248
|
+
/** Matches two or more consecutive underscores. */
|
|
1249
|
+
const MULTIPLE_UNDERSCORES_REGEX = /_+/g;
|
|
1250
|
+
/** Matches the `.magento` segment in plugin webhook method names (e.g. `plugin.magento.foo`). */
|
|
1251
|
+
const PLUGIN_MAGENTO_REGEX = /^plugin\.magento\./;
|
|
1252
|
+
const ENVIRONMENT_PRODUCTION = "production";
|
|
1253
|
+
const ENVIRONMENT_STAGING = "staging";
|
|
1254
|
+
/**
|
|
1255
|
+
* Validates that no modification webhooks in the app config conflict with webhooks
|
|
1256
|
+
* already registered in Commerce by another app.
|
|
1257
|
+
*
|
|
1258
|
+
* A conflict is: Commerce has a webhook with the same `webhook_method` and `webhook_type`
|
|
1259
|
+
* that does NOT belong to this app (i.e. different `batch_name` or `hook_name` after prefix).
|
|
1260
|
+
*
|
|
1261
|
+
* Returns a `ValidationIssue` with code `WEBHOOK_CONFLICTS` and `details.conflictedWebhooks` listing
|
|
1262
|
+
* every conflicting Commerce webhook when conflicts are found, or an empty array otherwise.
|
|
1263
|
+
*
|
|
1264
|
+
* @param config - The app config (must have a non-empty `webhooks` array).
|
|
1265
|
+
* @param context - The webhooks execution context (provides the Commerce API client and logger).
|
|
1266
|
+
*/
|
|
1267
|
+
async function validateWebhookConflicts(config, context) {
|
|
1268
|
+
const { logger, commerceWebhooksClient } = context;
|
|
1269
|
+
const modificationWebhooks = config.webhooks.filter((entry) => entry.category === "modification");
|
|
1270
|
+
if (modificationWebhooks.length === 0) {
|
|
1271
|
+
logger.debug("No modification webhooks to validate, skipping conflict check.");
|
|
1272
|
+
return [];
|
|
1273
|
+
}
|
|
1274
|
+
logger.debug(`Validating ${modificationWebhooks.length} modification webhook(s) for conflicts...`);
|
|
1275
|
+
const existingWebhooks = await commerceWebhooksClient.getWebhookList();
|
|
1276
|
+
const idPrefix = buildWebhookIdPrefix(config.metadata.id);
|
|
1277
|
+
const conflictedWebhooks = [];
|
|
1278
|
+
for (const entry of modificationWebhooks) {
|
|
1279
|
+
const { webhook } = entry;
|
|
1280
|
+
const resolvedBatch = `${idPrefix}${webhook.batch_name}`;
|
|
1281
|
+
const resolvedHook = `${idPrefix}${webhook.hook_name}`;
|
|
1282
|
+
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)) {
|
|
1283
|
+
conflictedWebhooks.push({
|
|
1284
|
+
label: entry.label,
|
|
1285
|
+
...existing
|
|
1286
|
+
});
|
|
1287
|
+
break;
|
|
1288
|
+
}
|
|
1289
|
+
}
|
|
1290
|
+
if (conflictedWebhooks.length > 0) return [{
|
|
1291
|
+
code: "WEBHOOK_CONFLICTS",
|
|
1292
|
+
message: `Webhook conflicts detected: ${conflictedWebhooks.length} webhook(s) already registered for the same method and type by another app`,
|
|
1293
|
+
severity: "warning",
|
|
1294
|
+
details: { conflictedWebhooks }
|
|
1295
|
+
}];
|
|
1296
|
+
logger.info("No webhook conflicts found.");
|
|
1297
|
+
return [];
|
|
1298
|
+
}
|
|
1299
|
+
/**
|
|
1300
|
+
* Subscribes each webhook from the app config to Adobe Commerce.
|
|
1301
|
+
* Throws on the first failure, aborting any remaining subscriptions.
|
|
1302
|
+
*
|
|
1303
|
+
* @param config - The app config (must have a non-empty `webhooks` array).
|
|
1304
|
+
* @param context - The webhooks execution context (provides the Commerce API client and logger).
|
|
1305
|
+
*/
|
|
1306
|
+
async function createWebhookSubscriptions(config, context) {
|
|
1307
|
+
const { logger, commerceWebhooksClient, params } = context;
|
|
1308
|
+
logger.info(`Subscribing ${config.webhooks.length} webhook(s) to Commerce...`);
|
|
1309
|
+
const idPrefix = buildWebhookIdPrefix(config.metadata.id);
|
|
1310
|
+
const subscribedWebhooks = [];
|
|
1311
|
+
const existingWebhooks = await commerceWebhooksClient.getWebhookList();
|
|
1312
|
+
for (const entry of config.webhooks) {
|
|
1313
|
+
const { webhook } = entry;
|
|
1314
|
+
const resolvedUrl = "runtimeAction" in entry ? generateUrlForRuntimeAction(entry.runtimeAction) : entry.webhook.url;
|
|
1315
|
+
logger.debug(`Subscribing webhook "${getWebhookName(webhook)}" (runtimeAction: ${"runtimeAction" in entry ? entry.runtimeAction : "none"})`);
|
|
1316
|
+
const resolvedWebhook = {
|
|
1317
|
+
...webhook,
|
|
1318
|
+
url: resolvedUrl,
|
|
1319
|
+
batch_name: `${idPrefix}${webhook.batch_name}`,
|
|
1320
|
+
hook_name: `${idPrefix}${webhook.hook_name}`,
|
|
1321
|
+
..."runtimeAction" in entry && entry.requireAdobeAuth !== false && { developer_console_oauth: resolveDeveloperConsoleOAuthCredentials(params) }
|
|
1322
|
+
};
|
|
1323
|
+
subscribedWebhooks.push(await createOrGetWebhookSubscription(existingWebhooks, commerceWebhooksClient, resolvedWebhook, logger));
|
|
1324
|
+
}
|
|
1325
|
+
logger.info(`Webhook subscriptions complete: ${subscribedWebhooks.length} subscribed.`);
|
|
1326
|
+
return { subscribedWebhooks };
|
|
1327
|
+
}
|
|
1328
|
+
/**
|
|
1329
|
+
* Subscribes a single webhook to Commerce, skipping the API call if the webhook
|
|
1330
|
+
* is already subscribed (matched by webhook_method, webhook_type, batch_name, hook_name).
|
|
1331
|
+
*/
|
|
1332
|
+
async function createOrGetWebhookSubscription(existingWebhooks, client, resolvedWebhook, logger) {
|
|
1333
|
+
if (isAlreadySubscribed(existingWebhooks, resolvedWebhook)) {
|
|
1334
|
+
logger.info(`Webhook already subscribed, skipping: ${getWebhookName(resolvedWebhook)}`);
|
|
1335
|
+
return resolvedWebhook;
|
|
1336
|
+
}
|
|
1337
|
+
const subscribed = await createWebhookSubscription(client, resolvedWebhook);
|
|
1338
|
+
logger.info(`Subscribed webhook: ${getWebhookName(resolvedWebhook)}`);
|
|
1339
|
+
return subscribed;
|
|
1340
|
+
}
|
|
1341
|
+
/**
|
|
1342
|
+
* Subscribes a single webhook to Commerce, enriching the error with the webhook name
|
|
1343
|
+
* if the API responds with a string `message`.
|
|
1344
|
+
*/
|
|
1345
|
+
async function createWebhookSubscription(client, resolvedWebhook) {
|
|
1346
|
+
try {
|
|
1347
|
+
await client.subscribeWebhook(resolvedWebhook);
|
|
1348
|
+
return resolvedWebhook;
|
|
1349
|
+
} catch (err) {
|
|
1350
|
+
if (err instanceof ky.HTTPError) {
|
|
1351
|
+
let body;
|
|
1352
|
+
try {
|
|
1353
|
+
body = await err.response.json();
|
|
1354
|
+
} catch {
|
|
1355
|
+
throw err;
|
|
1356
|
+
}
|
|
1357
|
+
if (typeof body?.message === "string") throw new Error(`Webhook subscription failed for "${getWebhookName(resolvedWebhook)}": ${body.message}`);
|
|
1358
|
+
}
|
|
1359
|
+
throw err;
|
|
1360
|
+
}
|
|
1361
|
+
}
|
|
1362
|
+
/**
|
|
1363
|
+
* Resolves and validates the IMS credentials required for `developer_console_oauth`.
|
|
1364
|
+
*
|
|
1365
|
+
* Delegates parsing and validation to `resolveImsAuthParams` from `aio-commerce-lib-auth`,
|
|
1366
|
+
* which correctly handles `AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS` whether it arrives as a
|
|
1367
|
+
* real array or as a JSON-stringified array string.
|
|
1368
|
+
*/
|
|
1369
|
+
function resolveDeveloperConsoleOAuthCredentials(params) {
|
|
1370
|
+
const { AIO_COMMERCE_AUTH_IMS_ENVIRONMENT: imsEnvironment, ...imsParams } = params;
|
|
1371
|
+
const { clientId, clientSecrets, imsOrgId } = (0, _adobe_aio_commerce_lib_auth.resolveImsAuthParams)(imsParams);
|
|
1372
|
+
return {
|
|
1373
|
+
client_id: clientId,
|
|
1374
|
+
client_secret: clientSecrets[0],
|
|
1375
|
+
org_id: imsOrgId,
|
|
1376
|
+
environment: !imsEnvironment || String(imsEnvironment).startsWith("prod") ? ENVIRONMENT_PRODUCTION : ENVIRONMENT_STAGING
|
|
1377
|
+
};
|
|
1378
|
+
}
|
|
1379
|
+
/**
|
|
1380
|
+
* Returns true when the candidate webhook is already present in the existing subscription list,
|
|
1381
|
+
* matched by the four-part identity: webhook_method, webhook_type, batch_name, hook_name.
|
|
1382
|
+
*
|
|
1383
|
+
* `webhook_method` is normalised before comparison to handle the case where Commerce strips the
|
|
1384
|
+
* `.magento` segment from plugin webhook methods on storage
|
|
1385
|
+
* (e.g. `plugin.magento.foo` and `plugin.foo` are treated as the same method).
|
|
1386
|
+
*/
|
|
1387
|
+
function isAlreadySubscribed(existing, candidate) {
|
|
1388
|
+
const normalizedCandidate = normalizeWebhookMethod(candidate.webhook_method);
|
|
1389
|
+
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);
|
|
1390
|
+
}
|
|
1391
|
+
/**
|
|
1392
|
+
* Normalises a webhook method name by removing the `.magento` segment that Commerce
|
|
1393
|
+
* may drop when persisting plugin webhook methods.
|
|
1394
|
+
*
|
|
1395
|
+
* @example
|
|
1396
|
+
* normalizeWebhookMethod("plugin.magento.foo.bar") // → "plugin.foo.bar"
|
|
1397
|
+
* normalizeWebhookMethod("plugin.foo.bar") // → "plugin.foo.bar" (unchanged)
|
|
1398
|
+
*/
|
|
1399
|
+
function normalizeWebhookMethod(method) {
|
|
1400
|
+
return method.replace(PLUGIN_MAGENTO_REGEX, "plugin.");
|
|
1401
|
+
}
|
|
1402
|
+
/**
|
|
1403
|
+
* Generates a URL for a given runtime action using the AIO Runtime API host and namespace.
|
|
1404
|
+
* @param runtimeAction
|
|
1405
|
+
* @return The generated URL for the runtime action.
|
|
1406
|
+
*/
|
|
1407
|
+
function generateUrlForRuntimeAction(runtimeAction) {
|
|
1408
|
+
const namespace = process.env.__OW_NAMESPACE;
|
|
1409
|
+
if (!namespace) throw new Error(`Cannot generate URL for runtime action "${runtimeAction}": namespace environment variable is not set.`);
|
|
1410
|
+
return `https://${namespace}.adobeioruntime.net/api/v1/web/${runtimeAction}`;
|
|
1411
|
+
}
|
|
1412
|
+
/**
|
|
1413
|
+
* Builds a prefix string from the app ID to namespace webhook batch/hook names.
|
|
1414
|
+
* Non-identifier characters are replaced with underscores; consecutive underscores
|
|
1415
|
+
* are collapsed to one; a trailing underscore is appended. The result is lowercased
|
|
1416
|
+
* to ensure consistent matching regardless of input casing.
|
|
1417
|
+
*
|
|
1418
|
+
* @example
|
|
1419
|
+
* ```typescript
|
|
1420
|
+
* buildWebhookIdPrefix("my--app.v2") // => "my_app_v2_"
|
|
1421
|
+
* buildWebhookIdPrefix("MyApp") // => "myapp_"
|
|
1422
|
+
* ```
|
|
1423
|
+
* @param appId - The app ID to build the prefix from.
|
|
1424
|
+
* @return The built prefix string.
|
|
1425
|
+
*/
|
|
1426
|
+
function buildWebhookIdPrefix(appId) {
|
|
1427
|
+
const prefix = appId.toLowerCase().replace(NON_IDENTIFIER_CHAR_REGEX, "_").replace(MULTIPLE_UNDERSCORES_REGEX, "_");
|
|
1428
|
+
return prefix.endsWith("_") ? prefix : `${prefix}_`;
|
|
1429
|
+
}
|
|
1430
|
+
/**
|
|
1431
|
+
* Generates a name for a webhook based on its method and type.
|
|
1432
|
+
*
|
|
1433
|
+
* @param webhook
|
|
1434
|
+
* @return A string in the format "webhook_method:webhook_type" to identify the webhook.
|
|
1435
|
+
*/
|
|
1436
|
+
function getWebhookName(webhook) {
|
|
1437
|
+
return `${webhook.webhook_method}:${webhook.webhook_type}`;
|
|
1117
1438
|
}
|
|
1118
1439
|
|
|
1119
1440
|
//#endregion
|
|
@@ -1124,11 +1445,8 @@ const subscriptionsStep = defineLeafStep({
|
|
|
1124
1445
|
label: "Create Subscriptions",
|
|
1125
1446
|
description: "Creates webhook subscriptions in Adobe Commerce"
|
|
1126
1447
|
},
|
|
1127
|
-
|
|
1128
|
-
|
|
1129
|
-
logger.debug(config);
|
|
1130
|
-
return createWebhookSubscriptions(context);
|
|
1131
|
-
}
|
|
1448
|
+
validate: (config, context) => validateWebhookConflicts(config, context),
|
|
1449
|
+
run: (config, context) => createWebhookSubscriptions(config, context)
|
|
1132
1450
|
});
|
|
1133
1451
|
/** Branch step for setting up Commerce webhooks. */
|
|
1134
1452
|
const webhooksStep = defineBranchStep({
|
|
@@ -1137,7 +1455,8 @@ const webhooksStep = defineBranchStep({
|
|
|
1137
1455
|
label: "Webhooks",
|
|
1138
1456
|
description: "Sets up Commerce webhooks"
|
|
1139
1457
|
},
|
|
1140
|
-
when: hasWebhooks,
|
|
1458
|
+
when: require_webhooks.hasWebhooks,
|
|
1459
|
+
context: createWebhooksStepContext,
|
|
1141
1460
|
children: [subscriptionsStep]
|
|
1142
1461
|
});
|
|
1143
1462
|
|
|
@@ -1194,6 +1513,22 @@ function runInstallation(options) {
|
|
|
1194
1513
|
hooks
|
|
1195
1514
|
});
|
|
1196
1515
|
}
|
|
1516
|
+
/**
|
|
1517
|
+
* Runs pre-installation validation over the full step tree.
|
|
1518
|
+
*
|
|
1519
|
+
* Traverses the same step hierarchy used during installation but only calls
|
|
1520
|
+
* each step's optional `validate` handler rather than executing side effects.
|
|
1521
|
+
* Always resolves (never throws). Returns a structured result with per-step
|
|
1522
|
+
* issues and an aggregated summary.
|
|
1523
|
+
*/
|
|
1524
|
+
function runValidation(options) {
|
|
1525
|
+
const { validationContext, config } = options;
|
|
1526
|
+
return validateStepTree({
|
|
1527
|
+
rootStep: createRootInstallationStep(config),
|
|
1528
|
+
validationContext,
|
|
1529
|
+
config
|
|
1530
|
+
});
|
|
1531
|
+
}
|
|
1197
1532
|
|
|
1198
1533
|
//#endregion
|
|
1199
1534
|
Object.defineProperty(exports, 'createInitialInstallationState', {
|
|
@@ -1237,4 +1572,10 @@ Object.defineProperty(exports, 'runInstallation', {
|
|
|
1237
1572
|
get: function () {
|
|
1238
1573
|
return runInstallation;
|
|
1239
1574
|
}
|
|
1575
|
+
});
|
|
1576
|
+
Object.defineProperty(exports, 'runValidation', {
|
|
1577
|
+
enumerable: true,
|
|
1578
|
+
get: function () {
|
|
1579
|
+
return runValidation;
|
|
1580
|
+
}
|
|
1240
1581
|
});
|
|
@@ -1,4 +1,18 @@
|
|
|
1
|
-
|
|
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
|
+
const require_schemas = require('./schemas-nkIxa8sL.cjs');
|
|
2
16
|
let _adobe_aio_commerce_lib_core_responses = require("@adobe/aio-commerce-lib-core/responses");
|
|
3
17
|
let _adobe_aio_lib_core_logging = require("@adobe/aio-lib-core-logging");
|
|
4
18
|
_adobe_aio_lib_core_logging = require_schemas.__toESM(_adobe_aio_lib_core_logging);
|
|
@@ -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
|
//#region \0rolldown/runtime.js
|
|
2
16
|
var __create = Object.create;
|
|
3
17
|
var __defProp = Object.defineProperty;
|
|
@@ -35,6 +49,20 @@ const ALPHANUMERIC_OR_HYPHEN_REGEX = {
|
|
|
35
49
|
uppercase: /^[A-Z0-9-]+$/
|
|
36
50
|
};
|
|
37
51
|
/**
|
|
52
|
+
* A schema for a number value.
|
|
53
|
+
* @param name The name of the field this schema refers to.
|
|
54
|
+
*/
|
|
55
|
+
function numberValueSchema(name) {
|
|
56
|
+
return valibot.number(`Expected a number value for '${name}'`);
|
|
57
|
+
}
|
|
58
|
+
/**
|
|
59
|
+
* A schema for a positive number value (including zero).
|
|
60
|
+
* @param name The name of the field this schema refers to.
|
|
61
|
+
*/
|
|
62
|
+
function positiveNumberValueSchema(name) {
|
|
63
|
+
return valibot.pipe(numberValueSchema(name), valibot.minValue(0, `The value of ${name} must be a non-negative number`));
|
|
64
|
+
}
|
|
65
|
+
/**
|
|
38
66
|
* A schema for a string value.
|
|
39
67
|
* @param name The name of the field this schema refers to.
|
|
40
68
|
*/
|
|
@@ -90,6 +118,12 @@ Object.defineProperty(exports, 'nonEmptyStringValueSchema', {
|
|
|
90
118
|
return nonEmptyStringValueSchema;
|
|
91
119
|
}
|
|
92
120
|
});
|
|
121
|
+
Object.defineProperty(exports, 'positiveNumberValueSchema', {
|
|
122
|
+
enumerable: true,
|
|
123
|
+
get: function () {
|
|
124
|
+
return positiveNumberValueSchema;
|
|
125
|
+
}
|
|
126
|
+
});
|
|
93
127
|
Object.defineProperty(exports, 'stringValueSchema', {
|
|
94
128
|
enumerable: true,
|
|
95
129
|
get: function () {
|
|
@@ -1,5 +1,19 @@
|
|
|
1
|
-
|
|
2
|
-
|
|
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
|
+
const require_schemas = require('./schemas-nkIxa8sL.cjs');
|
|
16
|
+
const require_webhooks = require('./webhooks-CbZpv9y_.cjs');
|
|
3
17
|
let _adobe_aio_commerce_lib_core_error = require("@adobe/aio-commerce-lib-core/error");
|
|
4
18
|
let valibot = require("valibot");
|
|
5
19
|
valibot = require_schemas.__toESM(valibot);
|
|
@@ -35,7 +49,7 @@ const MetadataSchema = valibot.object({
|
|
|
35
49
|
id: require_schemas.alphaNumericOrHyphenSchema("application id (metadata.id)"),
|
|
36
50
|
displayName: valibot.pipe(nonEmptyString("application display name"), valibot.maxLength(MAX_DISPLAY_NAME_LENGTH, `The application display name must not be longer than ${MAX_DISPLAY_NAME_LENGTH} characters`)),
|
|
37
51
|
description: valibot.pipe(nonEmptyString("metadata description"), valibot.maxLength(MAX_DESCRIPTION_LENGTH, `The metadata description must not be longer than ${MAX_DESCRIPTION_LENGTH} characters`)),
|
|
38
|
-
version: valibot.pipe(nonEmptyString("version"), valibot.regex(SEMVER_REGEX, "The version must follow semantic versioning (semver) format"))
|
|
52
|
+
version: valibot.pipe(nonEmptyString("version"), valibot.regex(SEMVER_REGEX, "The version must follow semantic versioning (semver) format: Major.Minor.Patch (e.g., '1.0.0', '2.3.1')"))
|
|
39
53
|
});
|
|
40
54
|
/**
|
|
41
55
|
* Check if config has metadata.
|
|
@@ -52,8 +66,9 @@ function hasMetadata(config) {
|
|
|
52
66
|
const CommerceAppConfigSchema = valibot.looseObject({
|
|
53
67
|
metadata: MetadataSchema,
|
|
54
68
|
businessConfig: valibot.optional(_adobe_aio_commerce_lib_config.SchemaBusinessConfig),
|
|
55
|
-
eventing: valibot.optional(
|
|
56
|
-
installation: valibot.optional(
|
|
69
|
+
eventing: valibot.optional(require_webhooks.EventingSchema),
|
|
70
|
+
installation: valibot.optional(require_webhooks.InstallationSchema),
|
|
71
|
+
webhooks: valibot.optional(require_webhooks.WebhooksSchema)
|
|
57
72
|
});
|
|
58
73
|
|
|
59
74
|
//#endregion
|
|
@@ -62,29 +77,31 @@ const CommerceAppConfigSchema = valibot.looseObject({
|
|
|
62
77
|
const CommerceAppConfigSchemas = {
|
|
63
78
|
metadata: MetadataSchema,
|
|
64
79
|
businessConfig: _adobe_aio_commerce_lib_config.SchemaBusinessConfig,
|
|
65
|
-
eventing:
|
|
66
|
-
installation:
|
|
80
|
+
eventing: require_webhooks.EventingSchema,
|
|
81
|
+
installation: require_webhooks.InstallationSchema,
|
|
82
|
+
webhooks: require_webhooks.WebhooksSchema,
|
|
67
83
|
"businessConfig.schema": valibot.unwrap(_adobe_aio_commerce_lib_config.SchemaBusinessConfig.entries.schema),
|
|
68
|
-
"eventing.commerce": valibot.unwrap(
|
|
69
|
-
"eventing.external": valibot.unwrap(
|
|
70
|
-
"installation.customInstallationSteps": valibot.unwrap(
|
|
84
|
+
"eventing.commerce": valibot.unwrap(require_webhooks.EventingSchema.entries.commerce),
|
|
85
|
+
"eventing.external": valibot.unwrap(require_webhooks.EventingSchema.entries.external),
|
|
86
|
+
"installation.customInstallationSteps": valibot.unwrap(require_webhooks.InstallationSchema.entries.customInstallationSteps)
|
|
71
87
|
};
|
|
72
88
|
/**
|
|
73
89
|
* Get the config domains that are present in the config.
|
|
74
90
|
* @param config - The configuration to check.
|
|
75
91
|
*/
|
|
76
92
|
function getConfigDomains(config) {
|
|
77
|
-
const withCommerceEvents =
|
|
78
|
-
const withExternalEvents =
|
|
93
|
+
const withCommerceEvents = require_webhooks.hasCommerceEvents(config);
|
|
94
|
+
const withExternalEvents = require_webhooks.hasExternalEvents(config);
|
|
79
95
|
const domains = {
|
|
80
96
|
metadata: hasMetadata(config),
|
|
81
97
|
businessConfig: hasBusinessConfig(config),
|
|
82
98
|
eventing: withCommerceEvents || withExternalEvents,
|
|
83
|
-
installation:
|
|
99
|
+
installation: require_webhooks.hasCustomInstallation(config),
|
|
100
|
+
webhooks: require_webhooks.hasWebhooks(config),
|
|
84
101
|
"businessConfig.schema": hasBusinessConfigSchema(config),
|
|
85
102
|
"eventing.commerce": withCommerceEvents,
|
|
86
103
|
"eventing.external": withExternalEvents,
|
|
87
|
-
"installation.customInstallationSteps":
|
|
104
|
+
"installation.customInstallationSteps": require_webhooks.hasCustomInstallationSteps(config)
|
|
88
105
|
};
|
|
89
106
|
const domainsList = Object.entries(domains).filter(([_, value]) => value).map(([key]) => key);
|
|
90
107
|
return new Set(domainsList);
|