@adobe/aio-commerce-lib-app 0.2.0 → 0.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 +14 -0
- package/dist/cjs/actions/index.cjs +815 -170
- package/dist/cjs/actions/index.d.cts +2 -2
- package/dist/cjs/app-Dx0ca6oL.d.cts +181 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/get-config-schema.js.template +63 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/get-configuration.js.template +104 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/get-scope-tree.js.template +69 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/set-configuration.js.template +125 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/set-custom-scope-tree.js.template +83 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/sync-commerce-scopes.js.template +113 -0
- package/dist/cjs/commands/generate/actions/templates/business-configuration/unsync-commerce-scopes.js.template +56 -0
- package/dist/cjs/commands/index.cjs +1075 -119
- package/dist/cjs/config/index.cjs +20 -1
- package/dist/cjs/config/index.d.cts +532 -3
- package/dist/cjs/config-JQ_n-5Nk.cjs +565 -0
- package/dist/cjs/error-Byj1DVHZ.cjs +344 -0
- package/dist/cjs/{index-DS3IlISO.d.cts → index-C5SutkJQ.d.cts} +1 -1
- package/dist/cjs/logging-DYwr5WQk.cjs +25 -0
- package/dist/cjs/management/index.cjs +9 -1
- package/dist/cjs/management/index.d.cts +1 -1
- package/dist/cjs/management-Dm5h0E6l.cjs +1246 -0
- package/dist/es/actions/index.d.mts +2 -2
- package/dist/es/actions/index.mjs +813 -170
- package/dist/es/app-Cx1-6dn0.d.mts +181 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/get-config-schema.js.template +63 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/get-configuration.js.template +104 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/get-scope-tree.js.template +69 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/set-configuration.js.template +125 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/set-custom-scope-tree.js.template +83 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/sync-commerce-scopes.js.template +113 -0
- package/dist/es/commands/generate/actions/templates/business-configuration/unsync-commerce-scopes.js.template +56 -0
- package/dist/es/commands/index.mjs +1070 -119
- package/dist/es/config/index.d.mts +532 -3
- package/dist/es/config/index.mjs +4 -1
- package/dist/es/config-BSGerqCG.mjs +457 -0
- package/dist/es/error-P7JgUTds.mjs +251 -0
- package/dist/es/{index-DQepSWYP.d.mts → index-Bxr3zvCT.d.mts} +2 -2
- package/dist/es/logging-VgerMhp6.mjs +18 -0
- package/dist/es/management/index.d.mts +2 -2
- package/dist/es/management/index.mjs +3 -1
- package/dist/es/management-Y7pwEbNI.mjs +1204 -0
- package/package.json +20 -8
- package/dist/cjs/app-C4HhkXbP.d.cts +0 -451
- package/dist/cjs/error-yAk1zzvx.cjs +0 -1
- package/dist/cjs/management-CE3_DJw4.cjs +0 -2
- package/dist/cjs/parser-Dovux8ce.cjs +0 -1
- package/dist/cjs/schemas-CdaP-Exw.cjs +0 -1
- package/dist/es/app-CMpx3D7Y.d.mts +0 -451
- package/dist/es/chunk-VmiN0kV1.mjs +0 -1
- package/dist/es/error-hBHRgZ9R.mjs +0 -1
- package/dist/es/management-BM2WcbV6.mjs +0 -2
- package/dist/es/parser-DOVfvr9l.mjs +0 -1
- package/dist/es/schemas-eemlD-xS.mjs +0 -1
- /package/dist/cjs/commands/generate/actions/templates/{custom-scripts.js.template → app-management/custom-scripts.js.template} +0 -0
- /package/dist/cjs/commands/generate/actions/templates/{get-app-config.js.template → app-management/get-app-config.js.template} +0 -0
- /package/dist/cjs/commands/generate/actions/templates/{installation.js.template → app-management/installation.js.template} +0 -0
- /package/dist/es/commands/generate/actions/templates/{custom-scripts.js.template → app-management/custom-scripts.js.template} +0 -0
- /package/dist/es/commands/generate/actions/templates/{get-app-config.js.template → app-management/get-app-config.js.template} +0 -0
- /package/dist/es/commands/generate/actions/templates/{installation.js.template → app-management/installation.js.template} +0 -0
|
@@ -0,0 +1,1204 @@
|
|
|
1
|
+
import { a as hasExternalEvents, c as hasCustomInstallationSteps, i as hasEventing, r as hasCommerceEvents, t as stringifyError } from "./error-P7JgUTds.mjs";
|
|
2
|
+
import { t as inspect } from "./logging-VgerMhp6.mjs";
|
|
3
|
+
import camelcase from "camelcase";
|
|
4
|
+
import { resolveAuthParams } from "@adobe/aio-commerce-lib-auth";
|
|
5
|
+
import { resolveCommerceHttpClientParams, resolveIoEventsHttpClientParams } from "@adobe/aio-commerce-lib-api";
|
|
6
|
+
import { createCustomCommerceEventsApiClient, createEventProvider, createEventSubscription, getAllEventProviders, getAllEventSubscriptions, updateEventingConfiguration } from "@adobe/aio-commerce-lib-events/commerce";
|
|
7
|
+
import { createCustomAdobeIoEventsApiClient, createEventMetadataForProvider, createEventProvider as createEventProvider$1, createRegistration, getAllEventProviders as getAllEventProviders$1, getAllRegistrations } from "@adobe/aio-commerce-lib-events/io-events";
|
|
8
|
+
|
|
9
|
+
//#region source/management/installation/workflow/hooks.ts
|
|
10
|
+
/** Helper to call a hook if it exists. */
|
|
11
|
+
async function callHook(hooks, hookName, ...args) {
|
|
12
|
+
const hook = hooks?.[hookName];
|
|
13
|
+
if (hook) await hook(...args);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
//#endregion
|
|
17
|
+
//#region source/management/installation/workflow/step.ts
|
|
18
|
+
/** Check if a step is a leaf step. */
|
|
19
|
+
function isLeafStep(step) {
|
|
20
|
+
return step.type === "leaf";
|
|
21
|
+
}
|
|
22
|
+
/** Check if a step is a branch step. */
|
|
23
|
+
function isBranchStep(step) {
|
|
24
|
+
return step.type === "branch";
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Define a leaf step (executable, no children).
|
|
28
|
+
*
|
|
29
|
+
* @example
|
|
30
|
+
* ```typescript
|
|
31
|
+
* const createProviders = defineLeafStep({
|
|
32
|
+
* name: "providers",
|
|
33
|
+
* meta: { label: "Create Providers", description: "Creates I/O Events providers" },
|
|
34
|
+
* run: async ({ config, stepContext }) => {
|
|
35
|
+
* const { eventsClient } = stepContext;
|
|
36
|
+
* return eventsClient.createProvider(config.eventing);
|
|
37
|
+
* },
|
|
38
|
+
* });
|
|
39
|
+
* ```
|
|
40
|
+
*/
|
|
41
|
+
function defineLeafStep(options) {
|
|
42
|
+
return {
|
|
43
|
+
type: "leaf",
|
|
44
|
+
name: options.name,
|
|
45
|
+
meta: options.meta,
|
|
46
|
+
when: options.when,
|
|
47
|
+
run: options.run
|
|
48
|
+
};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Define a branch step (container with children, no runner).
|
|
52
|
+
*
|
|
53
|
+
* @example
|
|
54
|
+
* ```typescript
|
|
55
|
+
* const eventing = defineBranchStep({
|
|
56
|
+
* name: "eventing",
|
|
57
|
+
* meta: { label: "Eventing", description: "Sets up I/O Events" },
|
|
58
|
+
* when: hasEventing,
|
|
59
|
+
* context: async (ctx) => ({ eventsClient: await createEventsClient(ctx) }),
|
|
60
|
+
* children: [commerceEventsStep, externalEventsStep],
|
|
61
|
+
* });
|
|
62
|
+
* ```
|
|
63
|
+
*/
|
|
64
|
+
function defineBranchStep(options) {
|
|
65
|
+
return {
|
|
66
|
+
type: "branch",
|
|
67
|
+
name: options.name,
|
|
68
|
+
meta: options.meta,
|
|
69
|
+
when: options.when,
|
|
70
|
+
context: options.context,
|
|
71
|
+
children: options.children
|
|
72
|
+
};
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
//#endregion
|
|
76
|
+
//#region source/management/installation/workflow/utils.ts
|
|
77
|
+
/** Returns the current time as an ISO string. */
|
|
78
|
+
function nowIsoString() {
|
|
79
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
80
|
+
}
|
|
81
|
+
/** Sets a value at a nested path in the data object. */
|
|
82
|
+
function setAtPath(data, path, value) {
|
|
83
|
+
const lastKey = path.at(-1);
|
|
84
|
+
if (!lastKey) return;
|
|
85
|
+
let current = data;
|
|
86
|
+
for (let i = 0; i < path.length - 1; i++) {
|
|
87
|
+
const key = path[i];
|
|
88
|
+
current[key] ??= {};
|
|
89
|
+
current = current[key];
|
|
90
|
+
}
|
|
91
|
+
current[lastKey] = value;
|
|
92
|
+
}
|
|
93
|
+
/** Gets a value at a nested path in the data object. */
|
|
94
|
+
function getAtPath(data, path) {
|
|
95
|
+
let current = data;
|
|
96
|
+
for (const key of path) {
|
|
97
|
+
if (current == null || typeof current !== "object") return;
|
|
98
|
+
current = current[key];
|
|
99
|
+
}
|
|
100
|
+
return current;
|
|
101
|
+
}
|
|
102
|
+
/** Creates an installation error from an exception. */
|
|
103
|
+
function createInstallationError(err, path, key = "STEP_EXECUTION_FAILED") {
|
|
104
|
+
return {
|
|
105
|
+
path,
|
|
106
|
+
key,
|
|
107
|
+
message: err instanceof Error ? err.message : String(err)
|
|
108
|
+
};
|
|
109
|
+
}
|
|
110
|
+
/** Creates a succeeded installation state. */
|
|
111
|
+
function createSucceededState(base) {
|
|
112
|
+
return {
|
|
113
|
+
...base,
|
|
114
|
+
status: "succeeded",
|
|
115
|
+
completedAt: nowIsoString()
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
/** Creates a failed installation state. */
|
|
119
|
+
function createFailedState(base, error) {
|
|
120
|
+
return {
|
|
121
|
+
...base,
|
|
122
|
+
status: "failed",
|
|
123
|
+
completedAt: nowIsoString(),
|
|
124
|
+
error
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
//#endregion
|
|
129
|
+
//#region source/management/installation/workflow/runner.ts
|
|
130
|
+
/**
|
|
131
|
+
* Creates an initial installation state from a root step and config.
|
|
132
|
+
*
|
|
133
|
+
* Filters steps based on their `when` conditions and builds a
|
|
134
|
+
* tree structure with all steps set to "pending".
|
|
135
|
+
*/
|
|
136
|
+
function createInitialState(options) {
|
|
137
|
+
const { rootStep, config } = options;
|
|
138
|
+
return {
|
|
139
|
+
id: crypto.randomUUID(),
|
|
140
|
+
startedAt: nowIsoString(),
|
|
141
|
+
status: "in-progress",
|
|
142
|
+
step: buildInitialStepStatus(rootStep, config, []),
|
|
143
|
+
data: null
|
|
144
|
+
};
|
|
145
|
+
}
|
|
146
|
+
/**
|
|
147
|
+
* Executes a workflow from an initial state. Returns the final state (never throws).
|
|
148
|
+
*/
|
|
149
|
+
async function executeWorkflow(options) {
|
|
150
|
+
const { rootStep, installationContext, config, initialState, hooks } = options;
|
|
151
|
+
const step = structuredClone(initialState.step);
|
|
152
|
+
const context = {
|
|
153
|
+
installationContext,
|
|
154
|
+
config,
|
|
155
|
+
id: initialState.id,
|
|
156
|
+
startedAt: initialState.startedAt,
|
|
157
|
+
step,
|
|
158
|
+
data: null,
|
|
159
|
+
error: null,
|
|
160
|
+
hooks
|
|
161
|
+
};
|
|
162
|
+
await callHook(hooks, "onInstallationStart", snapshot(context));
|
|
163
|
+
try {
|
|
164
|
+
await executeStep(rootStep, context.step, {}, context);
|
|
165
|
+
const succeeded = createSucceededState({
|
|
166
|
+
id: context.id,
|
|
167
|
+
startedAt: context.startedAt,
|
|
168
|
+
step: context.step,
|
|
169
|
+
data: context.data
|
|
170
|
+
});
|
|
171
|
+
await callHook(hooks, "onInstallationSuccess", succeeded);
|
|
172
|
+
return succeeded;
|
|
173
|
+
} catch (err) {
|
|
174
|
+
const error = context.error ?? createInstallationError(err, [], "INSTALLATION_FAILED");
|
|
175
|
+
const failed = createFailedState({
|
|
176
|
+
step: context.step,
|
|
177
|
+
id: context.id,
|
|
178
|
+
startedAt: context.startedAt,
|
|
179
|
+
data: context.data
|
|
180
|
+
}, error);
|
|
181
|
+
await callHook(hooks, "onInstallationFailure", failed);
|
|
182
|
+
return failed;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
/**
|
|
186
|
+
* Builds initial step status from a step definition.
|
|
187
|
+
* Filters steps based on their `when` conditions.
|
|
188
|
+
*/
|
|
189
|
+
function buildInitialStepStatus(step, config, parentPath) {
|
|
190
|
+
const path = [...parentPath, step.name];
|
|
191
|
+
const children = [];
|
|
192
|
+
if (isBranchStep(step) && step.children.length > 0) for (const child of step.children) {
|
|
193
|
+
if (child.when && !child.when(config)) continue;
|
|
194
|
+
children.push(buildInitialStepStatus(child, config, path));
|
|
195
|
+
}
|
|
196
|
+
return {
|
|
197
|
+
id: crypto.randomUUID(),
|
|
198
|
+
name: step.name,
|
|
199
|
+
path,
|
|
200
|
+
meta: step.meta,
|
|
201
|
+
status: "pending",
|
|
202
|
+
children
|
|
203
|
+
};
|
|
204
|
+
}
|
|
205
|
+
/** Snapshot current execution as InProgressInstallationState. */
|
|
206
|
+
function snapshot(context) {
|
|
207
|
+
return {
|
|
208
|
+
id: context.id,
|
|
209
|
+
startedAt: context.startedAt,
|
|
210
|
+
status: "in-progress",
|
|
211
|
+
step: context.step,
|
|
212
|
+
data: context.data
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
/** Executes a single step (branch or leaf) recursively. */
|
|
216
|
+
async function executeStep(step, stepStatus, inherited, context) {
|
|
217
|
+
const { path } = stepStatus;
|
|
218
|
+
const isLeaf = isLeafStep(step);
|
|
219
|
+
stepStatus.status = "in-progress";
|
|
220
|
+
await callHook(context.hooks, "onStepStart", {
|
|
221
|
+
path,
|
|
222
|
+
stepName: step.name,
|
|
223
|
+
isLeaf
|
|
224
|
+
}, snapshot(context));
|
|
225
|
+
try {
|
|
226
|
+
if (isBranchStep(step)) await executeBranchStep(step, stepStatus, inherited, context);
|
|
227
|
+
else if (isLeafStep(step)) await executeLeafStep(step, stepStatus, inherited, context);
|
|
228
|
+
stepStatus.status = "succeeded";
|
|
229
|
+
context.data ??= {};
|
|
230
|
+
await callHook(context.hooks, "onStepSuccess", {
|
|
231
|
+
path,
|
|
232
|
+
stepName: step.name,
|
|
233
|
+
isLeaf,
|
|
234
|
+
result: getAtPath(context.data, path)
|
|
235
|
+
}, snapshot(context));
|
|
236
|
+
} catch (err) {
|
|
237
|
+
stepStatus.status = "failed";
|
|
238
|
+
context.error ??= createInstallationError(err, path);
|
|
239
|
+
await callHook(context.hooks, "onStepFailure", {
|
|
240
|
+
path,
|
|
241
|
+
stepName: step.name,
|
|
242
|
+
isLeaf,
|
|
243
|
+
error: context.error
|
|
244
|
+
}, snapshot(context));
|
|
245
|
+
throw err;
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
/** Executes a branch step by processing its children. */
|
|
249
|
+
async function executeBranchStep(step, stepStatus, inherited, context) {
|
|
250
|
+
let childContext = inherited;
|
|
251
|
+
if (step.context) {
|
|
252
|
+
const stepContext = await step.context(context.installationContext);
|
|
253
|
+
childContext = {
|
|
254
|
+
...inherited,
|
|
255
|
+
...stepContext
|
|
256
|
+
};
|
|
257
|
+
}
|
|
258
|
+
for (const child of stepStatus.children) {
|
|
259
|
+
const childStep = step.children.find((c) => c.name === child.name);
|
|
260
|
+
if (!childStep) throw new Error(`Step "${child.name}" not found`);
|
|
261
|
+
await executeStep(childStep, child, childContext, context);
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
/** Executes a leaf step and stores its result. */
|
|
265
|
+
async function executeLeafStep(step, stepStatus, inherited, context) {
|
|
266
|
+
const executionContext = {
|
|
267
|
+
...context.installationContext,
|
|
268
|
+
...inherited
|
|
269
|
+
};
|
|
270
|
+
const result = await step.run(context.config, executionContext);
|
|
271
|
+
context.data ??= {};
|
|
272
|
+
setAtPath(context.data, stepStatus.path, result);
|
|
273
|
+
}
|
|
274
|
+
|
|
275
|
+
//#endregion
|
|
276
|
+
//#region source/management/installation/workflow/types.ts
|
|
277
|
+
/** Type guard for pending installation state. */
|
|
278
|
+
/** Type guard for in-progress installation state. */
|
|
279
|
+
function isInProgressState(state) {
|
|
280
|
+
return state.status === "in-progress";
|
|
281
|
+
}
|
|
282
|
+
/** Type guard for succeeded installation state. */
|
|
283
|
+
function isSucceededState(state) {
|
|
284
|
+
return state.status === "succeeded";
|
|
285
|
+
}
|
|
286
|
+
/** Type guard for failed installation state. */
|
|
287
|
+
function isFailedState(state) {
|
|
288
|
+
return state.status === "failed";
|
|
289
|
+
}
|
|
290
|
+
/** Type guard for completed installation state (succeeded or failed). */
|
|
291
|
+
function isCompletedState(state) {
|
|
292
|
+
return state.status === "succeeded" || state.status === "failed";
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
//#endregion
|
|
296
|
+
//#region source/management/installation/custom-installation/custom-scripts.ts
|
|
297
|
+
/**
|
|
298
|
+
* Creates a leaf step for executing a single custom installation script.
|
|
299
|
+
*/
|
|
300
|
+
function createCustomScriptStep(scriptConfig) {
|
|
301
|
+
const { script, name, description } = scriptConfig;
|
|
302
|
+
return defineLeafStep({
|
|
303
|
+
name: camelcase(name),
|
|
304
|
+
meta: {
|
|
305
|
+
label: name,
|
|
306
|
+
description
|
|
307
|
+
},
|
|
308
|
+
run: async (config, context) => {
|
|
309
|
+
const { logger } = context;
|
|
310
|
+
const customScripts = context.customScripts || {};
|
|
311
|
+
logger.info(`Executing custom installation script: ${name}`);
|
|
312
|
+
logger.debug(`Script path: ${script}`);
|
|
313
|
+
const scriptModule = customScripts[script];
|
|
314
|
+
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.`);
|
|
315
|
+
if (typeof scriptModule !== "object" || !("default" in scriptModule)) throw new Error(`Script ${script} must export a default function. Use defineCustomInstallationStep helper.`);
|
|
316
|
+
const runFunction = scriptModule.default;
|
|
317
|
+
if (typeof runFunction !== "function") throw new Error(`Script ${script} default export must be a function, got ${typeof runFunction}`);
|
|
318
|
+
const scriptResult = await runFunction(config, context);
|
|
319
|
+
logger.info(`Successfully executed script: ${name}`);
|
|
320
|
+
return {
|
|
321
|
+
script,
|
|
322
|
+
data: scriptResult
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
});
|
|
326
|
+
}
|
|
327
|
+
/**
|
|
328
|
+
* Creates child steps dynamically based on the custom installation scripts
|
|
329
|
+
* defined in the configuration. Each script becomes a separate leaf step.
|
|
330
|
+
*/
|
|
331
|
+
function createCustomScriptSteps(config) {
|
|
332
|
+
if (!hasCustomInstallationSteps(config)) return [];
|
|
333
|
+
const steps = config.installation.customInstallationSteps;
|
|
334
|
+
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.");
|
|
335
|
+
return steps.map((scriptConfig) => createCustomScriptStep(scriptConfig));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
//#endregion
|
|
339
|
+
//#region source/management/installation/custom-installation/branch.ts
|
|
340
|
+
/** Root custom installation step that executes custom installation scripts. */
|
|
341
|
+
const customInstallationStepBase = defineBranchStep({
|
|
342
|
+
name: "customInstallationSteps",
|
|
343
|
+
meta: {
|
|
344
|
+
label: "Custom Installation Steps",
|
|
345
|
+
description: "Executes custom installation scripts defined in the application configuration"
|
|
346
|
+
},
|
|
347
|
+
when: hasCustomInstallationSteps,
|
|
348
|
+
children: []
|
|
349
|
+
});
|
|
350
|
+
/**
|
|
351
|
+
* Creates the custom installation step with dynamic children based on config.
|
|
352
|
+
* Each custom script becomes a direct child step.
|
|
353
|
+
*/
|
|
354
|
+
function createCustomInstallationStep(config) {
|
|
355
|
+
return {
|
|
356
|
+
...customInstallationStepBase,
|
|
357
|
+
children: createCustomScriptSteps(config)
|
|
358
|
+
};
|
|
359
|
+
}
|
|
360
|
+
|
|
361
|
+
//#endregion
|
|
362
|
+
//#region source/management/installation/custom-installation/define.ts
|
|
363
|
+
/**
|
|
364
|
+
* Define a custom installation step with type-safe parameters.
|
|
365
|
+
*
|
|
366
|
+
* This helper provides type safety and IDE autocompletion for custom installation scripts.
|
|
367
|
+
* The handler function receives properly typed `config` and `context` parameters.
|
|
368
|
+
*
|
|
369
|
+
* @param handler - The installation step handler function
|
|
370
|
+
* @returns The same handler function (for use as default export)
|
|
371
|
+
*
|
|
372
|
+
* @example
|
|
373
|
+
* ```typescript
|
|
374
|
+
* import { defineCustomInstallationStep } from "@adobe/aio-commerce-lib-app/management";
|
|
375
|
+
*
|
|
376
|
+
* export default defineCustomInstallationStep(async (config, context) => {
|
|
377
|
+
* const { logger, params } = context;
|
|
378
|
+
*
|
|
379
|
+
* logger.info(`Setting up ${config.metadata.displayName}...`);
|
|
380
|
+
*
|
|
381
|
+
* // Your installation logic here
|
|
382
|
+
* // TypeScript will provide autocompletion for config and context
|
|
383
|
+
*
|
|
384
|
+
* return {
|
|
385
|
+
* status: "success",
|
|
386
|
+
* message: "Setup completed",
|
|
387
|
+
* };
|
|
388
|
+
* });
|
|
389
|
+
* ```
|
|
390
|
+
*/
|
|
391
|
+
function defineCustomInstallationStep(handler) {
|
|
392
|
+
return handler;
|
|
393
|
+
}
|
|
394
|
+
|
|
395
|
+
//#endregion
|
|
396
|
+
//#region source/management/installation/events/utils.ts
|
|
397
|
+
const COMMERCE_PROVIDER_TYPE = "dx_commerce_events";
|
|
398
|
+
const EXTERNAL_PROVIDER_TYPE = "3rd_party_custom_events";
|
|
399
|
+
const PROVIDER_TYPE_TO_LABEL = {
|
|
400
|
+
[COMMERCE_PROVIDER_TYPE]: "Commerce",
|
|
401
|
+
[EXTERNAL_PROVIDER_TYPE]: "External"
|
|
402
|
+
};
|
|
403
|
+
/**
|
|
404
|
+
* Generates a unique instance ID for the given event provider within the context of the provided config.
|
|
405
|
+
* @param metadata - The metadata of the application
|
|
406
|
+
* @param provider - The event provider for which to generate the instance ID
|
|
407
|
+
*/
|
|
408
|
+
function generateInstanceId(metadata, provider) {
|
|
409
|
+
const slugLabel = provider.label.toLowerCase().replace(/\s+/g, "-");
|
|
410
|
+
return `${metadata.id}-${provider.key ?? slugLabel}`;
|
|
411
|
+
}
|
|
412
|
+
/**
|
|
413
|
+
* Find an existing event provider by its instance ID.
|
|
414
|
+
* @param allProviders - The list of all existing event providers.
|
|
415
|
+
* @param instanceId - The instance ID to search for.
|
|
416
|
+
*/
|
|
417
|
+
function findExistingProvider(allProviders, instanceId) {
|
|
418
|
+
return allProviders.find((provider) => provider.instance_id === instanceId) ?? null;
|
|
419
|
+
}
|
|
420
|
+
/**
|
|
421
|
+
* Find existing event metadata by its event name.
|
|
422
|
+
* @param allMetadata - The list of all existing event metadata.
|
|
423
|
+
* @param eventName - The event name to search for.
|
|
424
|
+
*/
|
|
425
|
+
function findExistingProviderMetadata(allMetadata, eventName) {
|
|
426
|
+
return allMetadata.find((meta) => meta.event_code === eventName) ?? null;
|
|
427
|
+
}
|
|
428
|
+
/**
|
|
429
|
+
<<<<<<< HEAD
|
|
430
|
+
* Find existing event registrations by client ID and name.
|
|
431
|
+
* @param allRegistrations - The list of all existing event registrations.
|
|
432
|
+
* @param clientId - The client ID of the workspace where the registration was created.
|
|
433
|
+
* @param name - The name of the registration to search for.
|
|
434
|
+
*/
|
|
435
|
+
function findExistingRegistrations(allRegistrations, clientId, name) {
|
|
436
|
+
return allRegistrations.find((reg) => reg.client_id === clientId && reg.name === name);
|
|
437
|
+
}
|
|
438
|
+
/**
|
|
439
|
+
* Generates a namespaced event name by combining the application ID with the event name.
|
|
440
|
+
* @param metadata
|
|
441
|
+
* @param name
|
|
442
|
+
*/
|
|
443
|
+
function getNamespacedEvent(metadata, name) {
|
|
444
|
+
return `${metadata.id}.${name}`;
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* Get the fully qualified name of an event for I/O Events based on the provider type.
|
|
448
|
+
* @param name - The name of the event.
|
|
449
|
+
* @param providerType - The type of the event provider.
|
|
450
|
+
*/
|
|
451
|
+
function getIoEventCode(name, providerType) {
|
|
452
|
+
return providerType === COMMERCE_PROVIDER_TYPE ? `com.adobe.commerce.${name}` : name;
|
|
453
|
+
}
|
|
454
|
+
/**
|
|
455
|
+
* Generates a registration name and description based on the provider, events, and runtime action.
|
|
456
|
+
* @param provider - The provider this registration is associated to.
|
|
457
|
+
* @param runtimeAction - The runtime action this registration points to.
|
|
458
|
+
*/
|
|
459
|
+
function getRegistrationName(provider, runtimeAction) {
|
|
460
|
+
const providerLabel = PROVIDER_TYPE_TO_LABEL[provider.provider_metadata] ?? "Unknown";
|
|
461
|
+
const [packageName, actionName] = runtimeAction.split("/").map(kebabToTitleCase);
|
|
462
|
+
return `${providerLabel} Event Registration: ${actionName} (${packageName})`;
|
|
463
|
+
}
|
|
464
|
+
/**
|
|
465
|
+
* Generates a registration name and description based on the provider, events, and runtime action.
|
|
466
|
+
* @param provider - The provider this registration is associated to.
|
|
467
|
+
* @param events - The events routed by this registration.
|
|
468
|
+
* @param runtimeAction - The runtime action this registration points to.
|
|
469
|
+
*/
|
|
470
|
+
function getRegistrationDescription(provider, events, runtimeAction) {
|
|
471
|
+
return [
|
|
472
|
+
"This registration was automatically created by @adobe/aio-commerce-lib-app. ",
|
|
473
|
+
`It belongs to the provider "${provider.label}" (instance ID: ${provider.instance_id}). `,
|
|
474
|
+
`It routes ${events.length} event(s) to the runtime action "${runtimeAction}".`
|
|
475
|
+
].join("\n");
|
|
476
|
+
}
|
|
477
|
+
/**
|
|
478
|
+
* Converts a kebab-case string to Title Case.
|
|
479
|
+
* @param str - The kebab-case string to convert.
|
|
480
|
+
*/
|
|
481
|
+
function kebabToTitleCase(str) {
|
|
482
|
+
return str.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1)).join(" ");
|
|
483
|
+
}
|
|
484
|
+
/**
|
|
485
|
+
* Groups events by their runtime actions. Since each event can have multiple
|
|
486
|
+
* runtime actions, this function creates a mapping where each unique runtime
|
|
487
|
+
* action points to all events that target it.
|
|
488
|
+
*
|
|
489
|
+
* @param events - The events to group by runtime actions.
|
|
490
|
+
*/
|
|
491
|
+
function groupEventsByRuntimeActions(events) {
|
|
492
|
+
const actionEventsMap = /* @__PURE__ */ new Map();
|
|
493
|
+
for (const event of events) for (const runtimeAction of event.runtimeActions) {
|
|
494
|
+
const existingEvents = actionEventsMap.get(runtimeAction) ?? [];
|
|
495
|
+
actionEventsMap.set(runtimeAction, [...existingEvents, event]);
|
|
496
|
+
}
|
|
497
|
+
return actionEventsMap;
|
|
498
|
+
}
|
|
499
|
+
function findExistingSubscription(allSubscriptions, eventName) {
|
|
500
|
+
return allSubscriptions.get(eventName) ?? null;
|
|
501
|
+
}
|
|
502
|
+
/**
|
|
503
|
+
* Creates a partially filled workspace configuration object based on the app credentials and parameters.
|
|
504
|
+
* This configuration is used when creating an event provider in Commerce.
|
|
505
|
+
*
|
|
506
|
+
* @param context - The execution context containing app credentials and parameters.
|
|
507
|
+
*/
|
|
508
|
+
function makeWorkspaceConfig(context) {
|
|
509
|
+
const { appData, params } = context;
|
|
510
|
+
const { consumerOrgId, orgName, projectId, projectName, projectTitle, workspaceId, workspaceName, workspaceTitle } = appData;
|
|
511
|
+
const authParams = resolveAuthParams(params);
|
|
512
|
+
if (authParams.strategy !== "ims") throw new Error("Failed to resolve IMS authentication parameters from the runtime action inputs.");
|
|
513
|
+
const { clientId, clientSecrets, technicalAccountEmail, technicalAccountId, imsOrgId, scopes } = authParams;
|
|
514
|
+
return { project: {
|
|
515
|
+
id: projectId,
|
|
516
|
+
name: projectName,
|
|
517
|
+
title: projectTitle,
|
|
518
|
+
org: {
|
|
519
|
+
id: consumerOrgId,
|
|
520
|
+
name: orgName,
|
|
521
|
+
ims_org_id: imsOrgId
|
|
522
|
+
},
|
|
523
|
+
workspace: {
|
|
524
|
+
id: workspaceId,
|
|
525
|
+
name: workspaceName,
|
|
526
|
+
title: workspaceTitle,
|
|
527
|
+
action_url: `https://${process.env.__OW_NAMESPACE}.adobeioruntime.net`,
|
|
528
|
+
app_url: `https://${process.env.__OW_NAMESPACE}.adobeio-static.net`,
|
|
529
|
+
details: { credentials: [{
|
|
530
|
+
id: "000000",
|
|
531
|
+
name: `aio-${workspaceId}`,
|
|
532
|
+
integration_type: "oauth_server_to_server",
|
|
533
|
+
oauth_server_to_server: {
|
|
534
|
+
client_id: clientId,
|
|
535
|
+
client_secrets: clientSecrets,
|
|
536
|
+
technical_account_email: technicalAccountEmail,
|
|
537
|
+
technical_account_id: technicalAccountId,
|
|
538
|
+
scopes: scopes.map((scope) => scope.trim())
|
|
539
|
+
}
|
|
540
|
+
}] }
|
|
541
|
+
}
|
|
542
|
+
} };
|
|
543
|
+
}
|
|
544
|
+
/**
|
|
545
|
+
* Retrieves the current existing data and returns it in a normalized way.
|
|
546
|
+
* @param context The execution context.
|
|
547
|
+
*/
|
|
548
|
+
async function getIoEventsExistingData(context) {
|
|
549
|
+
const { ioEventsClient, appData } = context;
|
|
550
|
+
const appCredentials = {
|
|
551
|
+
consumerOrgId: appData.consumerOrgId,
|
|
552
|
+
projectId: appData.projectId,
|
|
553
|
+
workspaceId: appData.workspaceId
|
|
554
|
+
};
|
|
555
|
+
const { _embedded: { providers: existingProviders } } = await ioEventsClient.getAllEventProviders({
|
|
556
|
+
consumerOrgId: appData.consumerOrgId,
|
|
557
|
+
withEventMetadata: true
|
|
558
|
+
});
|
|
559
|
+
const providersWithMetadata = existingProviders.map((providerHal) => {
|
|
560
|
+
const { _embedded, _links, ...providerData } = providerHal;
|
|
561
|
+
const actualMetadata = (_embedded?.eventmetadata ?? []).map(({ _embedded: _embedded$1, _links: _links$1, ...meta }) => ({
|
|
562
|
+
...meta,
|
|
563
|
+
sample: _embedded$1?.sample_event ?? null
|
|
564
|
+
}));
|
|
565
|
+
return {
|
|
566
|
+
...providerData,
|
|
567
|
+
metadata: actualMetadata
|
|
568
|
+
};
|
|
569
|
+
});
|
|
570
|
+
const { _embedded: { registrations: registrationsHal } } = await ioEventsClient.getAllRegistrations(appCredentials);
|
|
571
|
+
return {
|
|
572
|
+
providersWithMetadata,
|
|
573
|
+
registrations: registrationsHal.map(({ _links, ...reg }) => reg)
|
|
574
|
+
};
|
|
575
|
+
}
|
|
576
|
+
/**
|
|
577
|
+
* Retrieves the current existing Commerce eventing data and returns it in a normalized way.
|
|
578
|
+
* @param context - The execution context.
|
|
579
|
+
*/
|
|
580
|
+
async function getCommerceEventingExistingData(context) {
|
|
581
|
+
const { commerceEventsClient } = context;
|
|
582
|
+
const existingProviders = await commerceEventsClient.getAllEventProviders();
|
|
583
|
+
const existingSubscriptions = await commerceEventsClient.getAllEventSubscriptions();
|
|
584
|
+
return {
|
|
585
|
+
isDefaultWorkspaceConfigurationEmpty: existingProviders.some((provider) => !("id" in provider) && !provider.workspace_configuration?.trim()),
|
|
586
|
+
providers: existingProviders,
|
|
587
|
+
subscriptions: new Map(existingSubscriptions.map((subscription) => [subscription.name, subscription]))
|
|
588
|
+
};
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
//#endregion
|
|
592
|
+
//#region source/management/installation/events/helpers.ts
|
|
593
|
+
/**
|
|
594
|
+
* Creates an event provider if it does not already exist.
|
|
595
|
+
* @param params - The parameters necessary to create the provider.
|
|
596
|
+
*/
|
|
597
|
+
async function createIoEventProvider(params) {
|
|
598
|
+
const { context, provider } = params;
|
|
599
|
+
const { appData, ioEventsClient, logger } = context;
|
|
600
|
+
const appCredentials = {
|
|
601
|
+
consumerOrgId: appData.consumerOrgId,
|
|
602
|
+
projectId: appData.projectId,
|
|
603
|
+
workspaceId: appData.workspaceId
|
|
604
|
+
};
|
|
605
|
+
logger.info(`Creating provider "${provider.label}" with instance ID "${provider.instanceId}"`);
|
|
606
|
+
return ioEventsClient.createEventProvider({
|
|
607
|
+
...appCredentials,
|
|
608
|
+
label: provider.label,
|
|
609
|
+
providerType: provider.type,
|
|
610
|
+
instanceId: provider.instanceId,
|
|
611
|
+
description: provider.description
|
|
612
|
+
}).then((res) => {
|
|
613
|
+
logger.info(`Provider "${provider.label}" created with ID '${res.id}'`);
|
|
614
|
+
return res;
|
|
615
|
+
}).catch((error) => {
|
|
616
|
+
logger.error(`Failed to create provider "${provider.label}": ${stringifyError(error)}`);
|
|
617
|
+
throw error;
|
|
618
|
+
});
|
|
619
|
+
}
|
|
620
|
+
/**
|
|
621
|
+
* Creates or retrieves an existing I/O Events provider.
|
|
622
|
+
* @param params - Parameters needed to create or get the provider.
|
|
623
|
+
* @param existingData - Existing I/O Events data.
|
|
624
|
+
*/
|
|
625
|
+
async function createOrGetIoEventProvider(params, existingData) {
|
|
626
|
+
const { context, provider } = params;
|
|
627
|
+
const { logger } = context;
|
|
628
|
+
const { instanceId, ...providerData } = provider;
|
|
629
|
+
const existing = findExistingProvider(existingData, instanceId);
|
|
630
|
+
if (existing) {
|
|
631
|
+
logger.info(`Provider "${provider.label}" already exists with ID "${existing.id}", skipping creation.`);
|
|
632
|
+
return existing;
|
|
633
|
+
}
|
|
634
|
+
return createIoEventProvider({
|
|
635
|
+
context,
|
|
636
|
+
provider: {
|
|
637
|
+
...providerData,
|
|
638
|
+
instanceId
|
|
639
|
+
}
|
|
640
|
+
});
|
|
641
|
+
}
|
|
642
|
+
/**
|
|
643
|
+
* Creates an event metadata for a given provider and event.
|
|
644
|
+
* @param params - The parameters necessary to create the event metadata.
|
|
645
|
+
*/
|
|
646
|
+
async function createIoEventProviderEventMetadata(params) {
|
|
647
|
+
const { context, event, provider, type, metadata } = params;
|
|
648
|
+
const { appData, ioEventsClient, logger } = context;
|
|
649
|
+
const appCredentials = {
|
|
650
|
+
consumerOrgId: appData.consumerOrgId,
|
|
651
|
+
projectId: appData.projectId,
|
|
652
|
+
workspaceId: appData.workspaceId
|
|
653
|
+
};
|
|
654
|
+
const eventCode = getIoEventCode(getNamespacedEvent(metadata, event.name), type);
|
|
655
|
+
logger.info(`Creating event metadata (${eventCode}) for provider "${provider.label}" (instance ID: ${provider.instance_id}))`);
|
|
656
|
+
return ioEventsClient.createEventMetadataForProvider({
|
|
657
|
+
...appCredentials,
|
|
658
|
+
providerId: provider.id,
|
|
659
|
+
label: event.label,
|
|
660
|
+
description: event.description,
|
|
661
|
+
eventCode
|
|
662
|
+
}).then((res) => {
|
|
663
|
+
logger.info(`Event metadata "${event.label}" created for provider "${provider.label}"`);
|
|
664
|
+
return res;
|
|
665
|
+
}).catch((error) => {
|
|
666
|
+
logger.error(`Failed to create event metadata "${event.label}" for provider "${provider.label}": ${stringifyError(error)}`);
|
|
667
|
+
throw error;
|
|
668
|
+
});
|
|
669
|
+
}
|
|
670
|
+
/**
|
|
671
|
+
* Creates or retrieves existing I/O Events metadata for a given provider and event.
|
|
672
|
+
* @param params - The parameters necessary to create or get the event metadata.
|
|
673
|
+
* @param existingData - Existing I/O Events metadata for the provider.
|
|
674
|
+
*/
|
|
675
|
+
async function createOrGetIoProviderEventMetadata(params, existingData) {
|
|
676
|
+
const { context, event, provider, type, metadata } = params;
|
|
677
|
+
const { logger } = context;
|
|
678
|
+
const existing = findExistingProviderMetadata(existingData, getIoEventCode(getNamespacedEvent(metadata, event.name), type));
|
|
679
|
+
if (existing) {
|
|
680
|
+
logger.info(`Event metadata "${event.label}" already exists for provider "${provider.label}" (instance ID: ${provider.instance_id}), skipping creation.`);
|
|
681
|
+
return existing;
|
|
682
|
+
}
|
|
683
|
+
return createIoEventProviderEventMetadata(params);
|
|
684
|
+
}
|
|
685
|
+
/**
|
|
686
|
+
* Creates an event registration bound to the given provider ID for a set of events targeting the given runtime action.
|
|
687
|
+
* @param params - The parameters necessary to create the registration.
|
|
688
|
+
*/
|
|
689
|
+
async function createIoEventRegistration(params) {
|
|
690
|
+
const { context, events, provider, runtimeAction, metadata } = params;
|
|
691
|
+
const { appData, ioEventsClient, logger, params: runtimeParams } = context;
|
|
692
|
+
const appCredentials = {
|
|
693
|
+
consumerOrgId: appData.consumerOrgId,
|
|
694
|
+
projectId: appData.projectId,
|
|
695
|
+
workspaceId: appData.workspaceId
|
|
696
|
+
};
|
|
697
|
+
logger.info(`Creating registration(s) to runtime action "${runtimeAction}" for ${events.length} event(s) with provider "${provider.label}" (instance ID: ${provider.instance_id}))`);
|
|
698
|
+
const name = getRegistrationName(provider, runtimeAction);
|
|
699
|
+
const description = getRegistrationDescription(provider, events, runtimeAction);
|
|
700
|
+
const payload = {
|
|
701
|
+
...appCredentials,
|
|
702
|
+
name,
|
|
703
|
+
clientId: runtimeParams.AIO_COMMERCE_AUTH_IMS_CLIENT_ID,
|
|
704
|
+
description,
|
|
705
|
+
deliveryType: "webhook",
|
|
706
|
+
runtimeAction,
|
|
707
|
+
enabled: true,
|
|
708
|
+
eventsOfInterest: events.map((event) => ({
|
|
709
|
+
providerId: provider.id,
|
|
710
|
+
eventCode: getIoEventCode(getNamespacedEvent(metadata, event.name), provider.provider_metadata)
|
|
711
|
+
}))
|
|
712
|
+
};
|
|
713
|
+
return ioEventsClient.createRegistration(payload).then((res) => {
|
|
714
|
+
logger.info(`Registration "${name}" created for provider "${provider.label}" with ID '${res.id}'`);
|
|
715
|
+
return res;
|
|
716
|
+
}).catch((error) => {
|
|
717
|
+
logger.error(`Failed to create registration "${name}" for provider "${provider.label}": ${stringifyError(error)}`);
|
|
718
|
+
throw error;
|
|
719
|
+
});
|
|
720
|
+
}
|
|
721
|
+
/**
|
|
722
|
+
* Creates or retrieves an existing I/O Events registration.
|
|
723
|
+
* @param params - Parameters needed to create or get the registration.
|
|
724
|
+
* @param existingData - Existing I/O Events data.
|
|
725
|
+
*/
|
|
726
|
+
async function createOrGetIoEventRegistration(params, registrations) {
|
|
727
|
+
const { context, provider, runtimeAction } = params;
|
|
728
|
+
const { logger, params: runtimeParams } = context;
|
|
729
|
+
const name = getRegistrationName(provider, runtimeAction);
|
|
730
|
+
const existing = findExistingRegistrations(registrations, runtimeParams.AIO_COMMERCE_AUTH_IMS_CLIENT_ID, name);
|
|
731
|
+
if (existing) {
|
|
732
|
+
logger.info(`Registration "${name}" already exists for provider "${provider.label}" with ID '${existing.id}', skipping creation.`);
|
|
733
|
+
return existing;
|
|
734
|
+
}
|
|
735
|
+
return createIoEventRegistration(params);
|
|
736
|
+
}
|
|
737
|
+
/**
|
|
738
|
+
* Ensures Commerce Eventing is configured with the given configuration, updating it if it already exists.
|
|
739
|
+
* @param eventsClient
|
|
740
|
+
* @param params
|
|
741
|
+
* @param existingData
|
|
742
|
+
*/
|
|
743
|
+
async function configureCommerceEventing(params, existingData) {
|
|
744
|
+
const { context, config } = params;
|
|
745
|
+
const { commerceEventsClient, logger } = context;
|
|
746
|
+
logger.info("Starting configuration of the Commerce Eventing Module");
|
|
747
|
+
let updateParams = {
|
|
748
|
+
...config,
|
|
749
|
+
enabled: true
|
|
750
|
+
};
|
|
751
|
+
if (existingData.isDefaultWorkspaceConfigurationEmpty) {
|
|
752
|
+
if (!config.workspaceConfiguration) {
|
|
753
|
+
const message = "Workspace configuration is required to enable Commerce Eventing when there is not an existing one.";
|
|
754
|
+
logger.error(message);
|
|
755
|
+
throw new Error(message);
|
|
756
|
+
}
|
|
757
|
+
logger.info("Default provider workspace configuration already present, it will not be overriden");
|
|
758
|
+
const { workspaceConfiguration, ...rest } = updateParams;
|
|
759
|
+
updateParams = rest;
|
|
760
|
+
}
|
|
761
|
+
logger.info("Updating Commerce Eventing configuration with provided workspace configuration.");
|
|
762
|
+
return commerceEventsClient.updateEventingConfiguration(updateParams).then((success) => {
|
|
763
|
+
if (success) {
|
|
764
|
+
logger.info("Commerce Eventing Module configured successfully.");
|
|
765
|
+
return;
|
|
766
|
+
}
|
|
767
|
+
throw new Error("Something went wrong while configuring Commerce Eventing Module. Response was not successful but no error was thrown.");
|
|
768
|
+
}).catch((err) => {
|
|
769
|
+
logger.error(`Failed to configure Commerce Eventing Module: ${stringifyError(err)}`);
|
|
770
|
+
throw err;
|
|
771
|
+
});
|
|
772
|
+
}
|
|
773
|
+
/**
|
|
774
|
+
* Creates an event provider in Commerce for a given {@link IoEventProvider}.
|
|
775
|
+
* @param params - The parameters necessary to create the Commerce provider.
|
|
776
|
+
*/
|
|
777
|
+
async function createCommerceProvider(params) {
|
|
778
|
+
const { context, provider } = params;
|
|
779
|
+
const { commerceEventsClient, logger } = context;
|
|
780
|
+
logger.info(`Creating Commerce provider "${provider.label}" with instance ID "${provider.instance_id}"`);
|
|
781
|
+
return commerceEventsClient.createEventProvider({
|
|
782
|
+
providerId: provider.id,
|
|
783
|
+
instanceId: provider.instance_id,
|
|
784
|
+
label: provider.label,
|
|
785
|
+
description: provider.description,
|
|
786
|
+
associatedWorkspaceConfiguration: provider.workspaceConfiguration
|
|
787
|
+
}).then((res) => {
|
|
788
|
+
logger.info(`Commerce provider "${provider.label}" created with ID '${res.provider_id}'`);
|
|
789
|
+
return res;
|
|
790
|
+
}).catch((err) => {
|
|
791
|
+
logger.error(`Failed to create Commerce provider "${provider.label}": ${stringifyError(err)}`);
|
|
792
|
+
throw err;
|
|
793
|
+
});
|
|
794
|
+
}
|
|
795
|
+
/**
|
|
796
|
+
* Creates or retrieves an existing Commerce event provider.
|
|
797
|
+
* @param params - Parameters needed to create or get the Commerce provider.
|
|
798
|
+
* @param existingData - Existing Commerce providers.
|
|
799
|
+
*/
|
|
800
|
+
async function createOrGetCommerceProvider(params, existingProviders) {
|
|
801
|
+
const { context, provider } = params;
|
|
802
|
+
const { logger } = context;
|
|
803
|
+
const existing = findExistingProvider(existingProviders, provider.instance_id);
|
|
804
|
+
if (existing) {
|
|
805
|
+
logger.info(`Provider "${provider.label}" already exists with ID "${existing.id}", skipping creation.`);
|
|
806
|
+
return existing;
|
|
807
|
+
}
|
|
808
|
+
return createCommerceProvider(params);
|
|
809
|
+
}
|
|
810
|
+
/**
|
|
811
|
+
* Creates an event subscription in Commerce for the given event and provider.
|
|
812
|
+
* @param params - The parameters necessary to create the Commerce event subscription.
|
|
813
|
+
*/
|
|
814
|
+
async function createCommerceEventSubscription(params) {
|
|
815
|
+
const { context, metadata, provider, event } = params;
|
|
816
|
+
const { logger, commerceEventsClient } = context;
|
|
817
|
+
const eventName = getNamespacedEvent(metadata, event.config.name);
|
|
818
|
+
logger.info(`Creating event subscription for event "${event.config.name}" to provider "${provider.label}" (instance ID: ${provider.instance_id})`);
|
|
819
|
+
const eventSpec = {
|
|
820
|
+
name: eventName,
|
|
821
|
+
parent: event.config.name,
|
|
822
|
+
fields: event.config.fields,
|
|
823
|
+
providerId: provider.id,
|
|
824
|
+
destination: event.config.destination,
|
|
825
|
+
hipaaAuditRequired: event.config.hipaaAuditRequired,
|
|
826
|
+
prioritary: event.config.prioritary,
|
|
827
|
+
force: event.config.force
|
|
828
|
+
};
|
|
829
|
+
logger.debug(`Event subscription specification for event "${event.config.name}": ${inspect(eventSpec)}`);
|
|
830
|
+
return commerceEventsClient.createEventSubscription(eventSpec).then((_res) => {
|
|
831
|
+
logger.info(`Created event subscription for event "${event.config.name}" to provider "${provider.label} (instance ID: ${provider.instance_id})"`);
|
|
832
|
+
return eventSpec;
|
|
833
|
+
}).catch((err) => {
|
|
834
|
+
logger.error(`Failed to create event subscription for event "${event.config.name}" to provider "${provider.label}": ${stringifyError(err)}`);
|
|
835
|
+
throw err;
|
|
836
|
+
});
|
|
837
|
+
}
|
|
838
|
+
/**
|
|
839
|
+
* Creates or retrieves an existing Commerce event subscription.
|
|
840
|
+
* @param params - Parameters needed to create or get the subscription.
|
|
841
|
+
* @param existingData - Map of existing Commerce subscriptions keyed by event name.
|
|
842
|
+
*/
|
|
843
|
+
async function createOrGetCommerceEventSubscription(params, existingData) {
|
|
844
|
+
const { context, metadata, event } = params;
|
|
845
|
+
const { logger } = context;
|
|
846
|
+
const existing = findExistingSubscription(existingData, getNamespacedEvent(metadata, event.config.name));
|
|
847
|
+
if (existing) {
|
|
848
|
+
logger.info(`Subscription for event "${event.config.name}" already exists, skipping creation.`);
|
|
849
|
+
return {
|
|
850
|
+
name: existing.name,
|
|
851
|
+
parent: existing.parent,
|
|
852
|
+
fields: existing.fields,
|
|
853
|
+
providerId: existing.provider_id
|
|
854
|
+
};
|
|
855
|
+
}
|
|
856
|
+
return createCommerceEventSubscription(params);
|
|
857
|
+
}
|
|
858
|
+
/**
|
|
859
|
+
* Onboards a single event source to I/O Events by creating/retrieving the provider and its event metadata.
|
|
860
|
+
* This is the shared logic inside the loop for both commerce and external event installation steps.
|
|
861
|
+
*
|
|
862
|
+
* @param params - Configuration for onboarding a single event source
|
|
863
|
+
* @param existingData - Existing I/O Events data
|
|
864
|
+
*/
|
|
865
|
+
async function onboardIoEvents(params, existingData) {
|
|
866
|
+
const { providersWithMetadata, registrations } = existingData;
|
|
867
|
+
const { context, metadata, provider, providerType, events } = params;
|
|
868
|
+
const instanceId = generateInstanceId(metadata, provider);
|
|
869
|
+
const providerData = await createOrGetIoEventProvider({
|
|
870
|
+
context,
|
|
871
|
+
provider: {
|
|
872
|
+
...provider,
|
|
873
|
+
instanceId,
|
|
874
|
+
type: providerType
|
|
875
|
+
}
|
|
876
|
+
}, providersWithMetadata);
|
|
877
|
+
const metadataPromises = events.map((event) => createOrGetIoProviderEventMetadata({
|
|
878
|
+
metadata,
|
|
879
|
+
context,
|
|
880
|
+
type: providerType,
|
|
881
|
+
provider: providerData,
|
|
882
|
+
event
|
|
883
|
+
}, providersWithMetadata.find((p) => p.id === providerData.id)?.metadata ?? []));
|
|
884
|
+
const actionEventsMap = groupEventsByRuntimeActions(events);
|
|
885
|
+
const registrationPromises = Array.from(actionEventsMap.entries()).map(([runtimeAction, groupedEvents]) => createOrGetIoEventRegistration({
|
|
886
|
+
metadata,
|
|
887
|
+
context,
|
|
888
|
+
events: groupedEvents,
|
|
889
|
+
provider: providerData,
|
|
890
|
+
runtimeAction
|
|
891
|
+
}, registrations));
|
|
892
|
+
const [metadataData, registrationsData] = await Promise.all([Promise.all(metadataPromises), Promise.all(registrationPromises)]);
|
|
893
|
+
return {
|
|
894
|
+
providerData,
|
|
895
|
+
eventsData: events.map((event, index) => {
|
|
896
|
+
const eventRegistrations = event.runtimeActions.map((runtimeAction) => {
|
|
897
|
+
return registrationsData[Array.from(actionEventsMap.keys()).indexOf(runtimeAction)];
|
|
898
|
+
});
|
|
899
|
+
return {
|
|
900
|
+
config: {
|
|
901
|
+
...event,
|
|
902
|
+
providerType
|
|
903
|
+
},
|
|
904
|
+
data: {
|
|
905
|
+
metadata: metadataData[index],
|
|
906
|
+
registrations: eventRegistrations
|
|
907
|
+
}
|
|
908
|
+
};
|
|
909
|
+
})
|
|
910
|
+
};
|
|
911
|
+
}
|
|
912
|
+
/**
|
|
913
|
+
* Onboards Commerce Eventing by creating event subscriptions and configuring the Eventing Module.
|
|
914
|
+
* @param params - The parameters necessary to onboard Commerce Eventing.
|
|
915
|
+
*/
|
|
916
|
+
async function onboardCommerceEventing(params, existingData) {
|
|
917
|
+
const { context, metadata, ioData } = params;
|
|
918
|
+
const { events, provider, workspaceConfiguration } = ioData;
|
|
919
|
+
const instanceId = provider.instance_id;
|
|
920
|
+
const subscriptions = [];
|
|
921
|
+
await configureCommerceEventing({
|
|
922
|
+
context,
|
|
923
|
+
config: { workspaceConfiguration }
|
|
924
|
+
}, existingData);
|
|
925
|
+
const { workspace_configuration: _, ...commerceProviderData } = await createOrGetCommerceProvider({
|
|
926
|
+
context,
|
|
927
|
+
provider: {
|
|
928
|
+
id: provider.id,
|
|
929
|
+
instance_id: instanceId,
|
|
930
|
+
label: provider.label,
|
|
931
|
+
description: provider.description,
|
|
932
|
+
workspaceConfiguration
|
|
933
|
+
}
|
|
934
|
+
}, existingData.providers);
|
|
935
|
+
for (const event of events) subscriptions.push(await createOrGetCommerceEventSubscription({
|
|
936
|
+
context,
|
|
937
|
+
metadata,
|
|
938
|
+
provider,
|
|
939
|
+
event
|
|
940
|
+
}, existingData.subscriptions));
|
|
941
|
+
return {
|
|
942
|
+
commerceProvider: commerceProviderData,
|
|
943
|
+
subscriptions
|
|
944
|
+
};
|
|
945
|
+
}
|
|
946
|
+
|
|
947
|
+
//#endregion
|
|
948
|
+
//#region source/management/installation/events/commerce.ts
|
|
949
|
+
/** Leaf step for installing commerce event sources. */
|
|
950
|
+
const commerceEventsStep = defineLeafStep({
|
|
951
|
+
name: "commerce",
|
|
952
|
+
meta: {
|
|
953
|
+
label: "Configure Commerce Events",
|
|
954
|
+
description: "Sets up I/O Events for Adobe Commerce event sources"
|
|
955
|
+
},
|
|
956
|
+
when: hasCommerceEvents,
|
|
957
|
+
run: async (config, context) => {
|
|
958
|
+
const { logger } = context;
|
|
959
|
+
logger.debug("Starting installation of Commerce Events with config:", config);
|
|
960
|
+
const stepData = [];
|
|
961
|
+
const workspaceConfiguration = JSON.stringify(makeWorkspaceConfig(context));
|
|
962
|
+
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
963
|
+
const commerceEventingExistingData = await getCommerceEventingExistingData(context);
|
|
964
|
+
for (const { provider, events } of config.eventing.commerce) {
|
|
965
|
+
const { providerData, eventsData } = await onboardIoEvents({
|
|
966
|
+
context,
|
|
967
|
+
metadata: config.metadata,
|
|
968
|
+
provider,
|
|
969
|
+
events,
|
|
970
|
+
providerType: COMMERCE_PROVIDER_TYPE
|
|
971
|
+
}, existingIoEventsData);
|
|
972
|
+
const { commerceProvider, subscriptions } = await onboardCommerceEventing({
|
|
973
|
+
context,
|
|
974
|
+
metadata: config.metadata,
|
|
975
|
+
provider,
|
|
976
|
+
ioData: {
|
|
977
|
+
provider: providerData,
|
|
978
|
+
events: eventsData,
|
|
979
|
+
workspaceConfiguration
|
|
980
|
+
}
|
|
981
|
+
}, commerceEventingExistingData);
|
|
982
|
+
stepData.push({ provider: {
|
|
983
|
+
config: provider,
|
|
984
|
+
data: {
|
|
985
|
+
ioEvents: providerData,
|
|
986
|
+
commerce: commerceProvider,
|
|
987
|
+
events: eventsData.map(({ config: config$1, data }, index) => {
|
|
988
|
+
return {
|
|
989
|
+
config: config$1,
|
|
990
|
+
data: {
|
|
991
|
+
...data,
|
|
992
|
+
subscription: subscriptions[index]
|
|
993
|
+
}
|
|
994
|
+
};
|
|
995
|
+
})
|
|
996
|
+
}
|
|
997
|
+
} });
|
|
998
|
+
}
|
|
999
|
+
logger.debug("Completed Commerce Events installation step.");
|
|
1000
|
+
return stepData;
|
|
1001
|
+
}
|
|
1002
|
+
});
|
|
1003
|
+
|
|
1004
|
+
//#endregion
|
|
1005
|
+
//#region source/management/installation/events/context.ts
|
|
1006
|
+
/**
|
|
1007
|
+
* Create a custom Commerce API Client with only the operations we need for optimal package size.
|
|
1008
|
+
* @param params - The runtime action params to resolve the client params from.
|
|
1009
|
+
*/
|
|
1010
|
+
function createCommerceEventsApiClient(params) {
|
|
1011
|
+
const commerceClientParams = resolveCommerceHttpClientParams(params, { tryForwardAuthProvider: true });
|
|
1012
|
+
commerceClientParams.fetchOptions ??= {};
|
|
1013
|
+
commerceClientParams.fetchOptions.timeout = 1e3 * 60 * 2;
|
|
1014
|
+
return createCustomCommerceEventsApiClient(commerceClientParams, {
|
|
1015
|
+
createEventProvider,
|
|
1016
|
+
getAllEventProviders,
|
|
1017
|
+
createEventSubscription,
|
|
1018
|
+
getAllEventSubscriptions,
|
|
1019
|
+
updateEventingConfiguration
|
|
1020
|
+
});
|
|
1021
|
+
}
|
|
1022
|
+
/**
|
|
1023
|
+
* Create a custom Adobe I/O Events API Client with only the operations we need for optimal package size.
|
|
1024
|
+
* @param params - The runtime action params to resolve the client params from.
|
|
1025
|
+
*/
|
|
1026
|
+
function createIoEventsApiClient(params) {
|
|
1027
|
+
const ioEventsClientParams = resolveIoEventsHttpClientParams(params);
|
|
1028
|
+
ioEventsClientParams.fetchOptions ??= {};
|
|
1029
|
+
ioEventsClientParams.fetchOptions.timeout = 1e3 * 60 * 2;
|
|
1030
|
+
return createCustomAdobeIoEventsApiClient(ioEventsClientParams, {
|
|
1031
|
+
createEventProvider: createEventProvider$1,
|
|
1032
|
+
createEventMetadataForProvider,
|
|
1033
|
+
createRegistration,
|
|
1034
|
+
getAllEventProviders: getAllEventProviders$1,
|
|
1035
|
+
getAllRegistrations
|
|
1036
|
+
});
|
|
1037
|
+
}
|
|
1038
|
+
/** Creates the events step context with lazy-initialized API clients. */
|
|
1039
|
+
function createEventsStepContext(installation) {
|
|
1040
|
+
const { params } = installation;
|
|
1041
|
+
let commerceEventsClient = null;
|
|
1042
|
+
let ioEventsClient = null;
|
|
1043
|
+
return {
|
|
1044
|
+
get commerceEventsClient() {
|
|
1045
|
+
if (commerceEventsClient === null) commerceEventsClient = createCommerceEventsApiClient(params);
|
|
1046
|
+
return commerceEventsClient;
|
|
1047
|
+
},
|
|
1048
|
+
get ioEventsClient() {
|
|
1049
|
+
if (ioEventsClient === null) ioEventsClient = createIoEventsApiClient(params);
|
|
1050
|
+
return ioEventsClient;
|
|
1051
|
+
}
|
|
1052
|
+
};
|
|
1053
|
+
}
|
|
1054
|
+
|
|
1055
|
+
//#endregion
|
|
1056
|
+
//#region source/management/installation/events/external.ts
|
|
1057
|
+
/** Leaf step for installing external event sources. */
|
|
1058
|
+
const externalEventsStep = defineLeafStep({
|
|
1059
|
+
name: "external",
|
|
1060
|
+
meta: {
|
|
1061
|
+
label: "Configure External Events",
|
|
1062
|
+
description: "Sets up I/O Events for external event sources"
|
|
1063
|
+
},
|
|
1064
|
+
when: hasExternalEvents,
|
|
1065
|
+
run: async (config, context) => {
|
|
1066
|
+
const { logger } = context;
|
|
1067
|
+
logger.debug("Starting installation of External Events with config:", config);
|
|
1068
|
+
const stepData = [];
|
|
1069
|
+
const existingIoEventsData = await getIoEventsExistingData(context);
|
|
1070
|
+
for (const { provider, events } of config.eventing.external) {
|
|
1071
|
+
const { providerData, eventsData } = await onboardIoEvents({
|
|
1072
|
+
context,
|
|
1073
|
+
metadata: config.metadata,
|
|
1074
|
+
provider,
|
|
1075
|
+
events,
|
|
1076
|
+
providerType: EXTERNAL_PROVIDER_TYPE
|
|
1077
|
+
}, existingIoEventsData);
|
|
1078
|
+
stepData.push({ provider: {
|
|
1079
|
+
config: provider,
|
|
1080
|
+
data: {
|
|
1081
|
+
ioEvents: providerData,
|
|
1082
|
+
events: {
|
|
1083
|
+
config: events,
|
|
1084
|
+
data: eventsData
|
|
1085
|
+
}
|
|
1086
|
+
}
|
|
1087
|
+
} });
|
|
1088
|
+
}
|
|
1089
|
+
logger.debug("Completed External Events installation step.");
|
|
1090
|
+
return stepData;
|
|
1091
|
+
}
|
|
1092
|
+
});
|
|
1093
|
+
|
|
1094
|
+
//#endregion
|
|
1095
|
+
//#region source/management/installation/events/branch.ts
|
|
1096
|
+
/** Root eventing step that contains commerce and external event sub-steps. */
|
|
1097
|
+
const eventingStep = defineBranchStep({
|
|
1098
|
+
name: "eventing",
|
|
1099
|
+
meta: {
|
|
1100
|
+
label: "Eventing",
|
|
1101
|
+
description: "Sets up the I/O Events and the Commerce events required by the application"
|
|
1102
|
+
},
|
|
1103
|
+
when: hasEventing,
|
|
1104
|
+
context: createEventsStepContext,
|
|
1105
|
+
children: [commerceEventsStep, externalEventsStep]
|
|
1106
|
+
});
|
|
1107
|
+
|
|
1108
|
+
//#endregion
|
|
1109
|
+
//#region source/management/installation/webhooks/helpers.ts
|
|
1110
|
+
function createWebhookSubscriptions(context) {
|
|
1111
|
+
const { logger } = context;
|
|
1112
|
+
logger.info("Creating webhooks in Commerce");
|
|
1113
|
+
return { subscriptionsCreated: true };
|
|
1114
|
+
}
|
|
1115
|
+
|
|
1116
|
+
//#endregion
|
|
1117
|
+
//#region source/management/installation/webhooks/utils.ts
|
|
1118
|
+
/** Check if config has webhooks. */
|
|
1119
|
+
function hasWebhooks(config) {
|
|
1120
|
+
"webhooks" in config && Array.isArray(config.webhooks) && config.webhooks.length;
|
|
1121
|
+
return false;
|
|
1122
|
+
}
|
|
1123
|
+
|
|
1124
|
+
//#endregion
|
|
1125
|
+
//#region source/management/installation/webhooks/branch.ts
|
|
1126
|
+
const subscriptionsStep = defineLeafStep({
|
|
1127
|
+
name: "subscriptions",
|
|
1128
|
+
meta: {
|
|
1129
|
+
label: "Create Subscriptions",
|
|
1130
|
+
description: "Creates webhook subscriptions in Adobe Commerce"
|
|
1131
|
+
},
|
|
1132
|
+
run: (config, context) => {
|
|
1133
|
+
const { logger } = context;
|
|
1134
|
+
logger.debug(config);
|
|
1135
|
+
return createWebhookSubscriptions(context);
|
|
1136
|
+
}
|
|
1137
|
+
});
|
|
1138
|
+
/** Branch step for setting up Commerce webhooks. */
|
|
1139
|
+
const webhooksStep = defineBranchStep({
|
|
1140
|
+
name: "webhooks",
|
|
1141
|
+
meta: {
|
|
1142
|
+
label: "Webhooks",
|
|
1143
|
+
description: "Sets up Commerce webhooks"
|
|
1144
|
+
},
|
|
1145
|
+
when: hasWebhooks,
|
|
1146
|
+
children: [subscriptionsStep]
|
|
1147
|
+
});
|
|
1148
|
+
|
|
1149
|
+
//#endregion
|
|
1150
|
+
//#region source/management/installation/root.ts
|
|
1151
|
+
/**
|
|
1152
|
+
* Creates the default child steps built-in in the library with dynamic children based on the config.
|
|
1153
|
+
*/
|
|
1154
|
+
function createDefaultChildSteps(config) {
|
|
1155
|
+
return [
|
|
1156
|
+
eventingStep,
|
|
1157
|
+
webhooksStep,
|
|
1158
|
+
createCustomInstallationStep(config)
|
|
1159
|
+
];
|
|
1160
|
+
}
|
|
1161
|
+
/**
|
|
1162
|
+
* Creates a root installation step with dynamic children based on the config.
|
|
1163
|
+
*/
|
|
1164
|
+
function createRootInstallationStep(config) {
|
|
1165
|
+
return defineBranchStep({
|
|
1166
|
+
name: "installation",
|
|
1167
|
+
meta: {
|
|
1168
|
+
label: "Installation",
|
|
1169
|
+
description: "App installation workflow"
|
|
1170
|
+
},
|
|
1171
|
+
children: createDefaultChildSteps(config)
|
|
1172
|
+
});
|
|
1173
|
+
}
|
|
1174
|
+
|
|
1175
|
+
//#endregion
|
|
1176
|
+
//#region source/management/installation/runner.ts
|
|
1177
|
+
/**
|
|
1178
|
+
* Creates an initial installation state from the config and step definitions.
|
|
1179
|
+
* Filters steps based on their `when` conditions and builds a tree structure
|
|
1180
|
+
* with all steps set to "pending".
|
|
1181
|
+
*/
|
|
1182
|
+
function createInitialInstallationState(options) {
|
|
1183
|
+
const { config } = options;
|
|
1184
|
+
return createInitialState({
|
|
1185
|
+
rootStep: createRootInstallationStep(config),
|
|
1186
|
+
config
|
|
1187
|
+
});
|
|
1188
|
+
}
|
|
1189
|
+
/**
|
|
1190
|
+
* Runs the full installation workflow. Returns the final state (never throws).
|
|
1191
|
+
*/
|
|
1192
|
+
function runInstallation(options) {
|
|
1193
|
+
const { installationContext, config, initialState, hooks } = options;
|
|
1194
|
+
return executeWorkflow({
|
|
1195
|
+
rootStep: createRootInstallationStep(config),
|
|
1196
|
+
installationContext,
|
|
1197
|
+
config,
|
|
1198
|
+
initialState,
|
|
1199
|
+
hooks
|
|
1200
|
+
});
|
|
1201
|
+
}
|
|
1202
|
+
|
|
1203
|
+
//#endregion
|
|
1204
|
+
export { isFailedState as a, isCompletedState as i, runInstallation as n, isInProgressState as o, defineCustomInstallationStep as r, isSucceededState as s, createInitialInstallationState as t };
|