@adobe/aio-commerce-lib-app 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (62) hide show
  1. package/CHANGELOG.md +43 -0
  2. package/dist/cjs/actions/app-config.cjs +3 -3
  3. package/dist/cjs/actions/app-config.d.cts +1 -1
  4. package/dist/cjs/actions/config.cjs +31 -9
  5. package/dist/cjs/actions/installation.cjs +16 -13
  6. package/dist/cjs/actions/installation.d.cts +2 -2
  7. package/dist/cjs/actions/registration.cjs +41 -0
  8. package/dist/cjs/actions/registration.d.cts +33 -0
  9. package/dist/cjs/actions/scope-tree.cjs +5 -4
  10. package/dist/cjs/admin-ui-sdk-BiSopnKI.d.cts +665 -0
  11. package/dist/cjs/{app-DcQMhW2N.d.cts → app-C6kImBl7.d.cts} +242 -16
  12. package/dist/cjs/chunk-DS2yp5vX.cjs +48 -0
  13. package/dist/cjs/commands/index.cjs +582 -319
  14. package/dist/cjs/commands/templates/admin-ui-sdk/registration.js.template +20 -0
  15. package/dist/cjs/config/index.cjs +4 -3
  16. package/dist/cjs/config/index.d.cts +558 -62
  17. package/dist/cjs/{config-BppBKCFj.cjs → config-ct-qMOGj.cjs} +56 -25
  18. package/dist/cjs/{error-Dn7ool6k.cjs → error-C5LNOZ4f.cjs} +1 -1
  19. package/dist/cjs/{index-BVwQk9bx.d.cts → index-B5WKJ3hJ.d.cts} +20 -174
  20. package/dist/cjs/{logging-4s36JTiN.cjs → logging-BTevALnL.cjs} +2 -2
  21. package/dist/cjs/management/index.cjs +1 -1
  22. package/dist/cjs/management/index.d.cts +3 -2
  23. package/dist/cjs/{management-C6xG5bfl.cjs → management-D1QXg4YU.cjs} +128 -75
  24. package/dist/cjs/{router-KeQRduO3.cjs → router-CFErodHU.cjs} +2 -2
  25. package/dist/cjs/{schemas-nkIxa8sL.cjs → schemas-CukVpc19.cjs} +2 -34
  26. package/dist/cjs/step-CWYnKbga.d.cts +187 -0
  27. package/dist/cjs/{validate-CeUCT_7k.cjs → validate-b_bI4mDb.cjs} +4 -3
  28. package/dist/cjs/{webhooks-CLtDxwMa.cjs → webhooks-DS1BtgED.cjs} +157 -17
  29. package/dist/es/actions/app-config.d.mts +1 -1
  30. package/dist/es/actions/app-config.mjs +1 -1
  31. package/dist/es/actions/config.mjs +28 -7
  32. package/dist/es/actions/installation.d.mts +2 -2
  33. package/dist/es/actions/installation.mjs +12 -10
  34. package/dist/es/actions/registration.d.mts +33 -0
  35. package/dist/es/actions/registration.mjs +39 -0
  36. package/dist/es/actions/scope-tree.mjs +1 -1
  37. package/dist/es/admin-ui-sdk-CCo2SWiL.d.mts +667 -0
  38. package/dist/es/{app-DJr-mN9d.d.mts → app-DcY6dJiQ.d.mts} +242 -16
  39. package/dist/es/commands/index.mjs +575 -314
  40. package/dist/es/commands/templates/admin-ui-sdk/registration.js.template +20 -0
  41. package/dist/es/config/index.d.mts +558 -62
  42. package/dist/es/config/index.mjs +4 -4
  43. package/dist/es/{config-BohwKkQS.mjs → config-DGPFTk6w.mjs} +51 -20
  44. package/dist/es/{index-BENO5T7n.d.mts → index-CCov0odd.d.mts} +20 -174
  45. package/dist/es/management/index.d.mts +3 -2
  46. package/dist/es/management/index.mjs +1 -1
  47. package/dist/es/{management-ByHvVJ12.mjs → management-CCsOfniv.mjs} +124 -71
  48. package/dist/es/step-0eI2x4C9.d.mts +187 -0
  49. package/dist/es/{validate-CqJdGzyZ.mjs → validate-Dn3lLTGj.mjs} +2 -2
  50. package/dist/es/{webhooks-CYo-pqbR.mjs → webhooks-Dhrkkip7.mjs} +155 -16
  51. package/package.json +12 -9
  52. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/app-config.js.template +0 -0
  53. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/custom-scripts.js.template +0 -0
  54. /package/dist/cjs/commands/{generate/actions/templates → templates}/app-management/installation.js.template +0 -0
  55. /package/dist/cjs/commands/{generate/actions/templates → templates}/business-configuration/config.js.template +0 -0
  56. /package/dist/cjs/commands/{generate/actions/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  57. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/app-config.js.template +0 -0
  58. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/custom-scripts.js.template +0 -0
  59. /package/dist/es/commands/{generate/actions/templates → templates}/app-management/installation.js.template +0 -0
  60. /package/dist/es/commands/{generate/actions/templates → templates}/business-configuration/config.js.template +0 -0
  61. /package/dist/es/commands/{generate/actions/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  62. /package/dist/es/{schemas-CVXHgUhv.mjs → schemas-DOcnD16x.mjs} +0 -0
@@ -13,33 +13,276 @@
13
13
  * governing permissions and limitations under the License.
14
14
  */
15
15
 
16
- const require_schemas = require('../schemas-nkIxa8sL.cjs');
17
- const require_webhooks = require('../webhooks-CLtDxwMa.cjs');
18
- const require_validate = require('../validate-CeUCT_7k.cjs');
19
- const require_config = require('../config-BppBKCFj.cjs');
20
- const require_error = require('../error-Dn7ool6k.cjs');
21
- const require_logging = require('../logging-4s36JTiN.cjs');
16
+ const require_chunk = require('../chunk-DS2yp5vX.cjs');
17
+ const require_webhooks = require('../webhooks-DS1BtgED.cjs');
18
+ const require_validate = require('../validate-b_bI4mDb.cjs');
19
+ const require_config = require('../config-ct-qMOGj.cjs');
20
+ const require_error = require('../error-C5LNOZ4f.cjs');
21
+ const require_logging = require('../logging-BTevALnL.cjs');
22
22
  let _adobe_aio_commerce_lib_core_error = require("@adobe/aio-commerce-lib-core/error");
23
23
  let path = require("path");
24
- path = require_schemas.__toESM(path);
24
+ path = require_chunk.__toESM(path, 1);
25
25
  let fs = require("fs");
26
26
  let fs_promises = require("fs/promises");
27
27
  let consola = require("consola");
28
- consola = require_schemas.__toESM(consola);
28
+ consola = require_chunk.__toESM(consola, 1);
29
+ let prettier = require("prettier");
30
+ prettier = require_chunk.__toESM(prettier, 1);
29
31
  let url = require("url");
30
32
  let yaml = require("yaml");
31
33
  let consola_utils = require("consola/utils");
32
34
  let safe_stable_stringify = require("safe-stable-stringify");
35
+ safe_stable_stringify = require_chunk.__toESM(safe_stable_stringify, 1);
33
36
  let child_process = require("child_process");
34
37
  let _adobe_aio_lib_core_config = require("@adobe/aio-lib-core-config");
35
- _adobe_aio_lib_core_config = require_schemas.__toESM(_adobe_aio_lib_core_config);
38
+ _adobe_aio_lib_core_config = require_chunk.__toESM(_adobe_aio_lib_core_config, 1);
36
39
  let _adobe_aio_lib_ims = require("@adobe/aio-lib-ims");
37
- _adobe_aio_lib_ims = require_schemas.__toESM(_adobe_aio_lib_ims);
40
+ _adobe_aio_lib_ims = require_chunk.__toESM(_adobe_aio_lib_ims, 1);
38
41
  let dotenv = require("dotenv");
39
- dotenv = require_schemas.__toESM(dotenv);
40
- let prettier = require("prettier");
41
- prettier = require_schemas.__toESM(prettier);
42
+ dotenv = require_chunk.__toESM(dotenv, 1);
43
+ let _npmcli_package_json = require("@npmcli/package-json");
44
+ _npmcli_package_json = require_chunk.__toESM(_npmcli_package_json, 1);
45
+
46
+ //#region source/commands/constants.ts
47
+ /** The name of the package containing all the generated runtime actions */
48
+ const PACKAGE_NAME = "app-management";
49
+ /** The path to the directory containing the generated files */
50
+ const GENERATED_PATH = ".generated";
51
+ /** The folder name containing the runtime actions */
52
+ const ACTIONS_FOLDER = `actions/${PACKAGE_NAME}`;
53
+ /** The path to the directory containing the generated actions */
54
+ const GENERATED_ACTIONS_PATH = `${GENERATED_PATH}/${ACTIONS_FOLDER}`;
55
+ /** The name of the extension point for extensibility. */
56
+ const EXTENSIBILITY_EXTENSION_POINT_ID = "commerce/extensibility/1";
57
+ /** The name of the extension point for configuration. */
58
+ const CONFIGURATION_EXTENSION_POINT_ID = "commerce/configuration/1";
59
+ /** The name of the extension point for Admin UI SDK. */
60
+ const BACKEND_UI_EXTENSION_POINT_ID = "commerce/backend-ui/1";
61
+ /** The name of the package containing the admin UI SDK generated actions */
62
+ const ADMIN_UI_SDK_PACKAGE_NAME = "admin-ui-sdk";
63
+ /** The path to the directory containing the generated Admin UI SDK actions. */
64
+ const ADMIN_UI_SDK_ACTIONS_PATH = `${GENERATED_PATH}/actions/registration`;
65
+ /** The name of the configuration schema file */
66
+ const APP_MANIFEST_FILE = "app.commerce.manifest.json";
67
+ /** The name of the commerce app configuration file */
68
+ const COMMERCE_APP_CONFIG_FILE = "app.commerce.config";
69
+ /** The name of the configuration schema file */
70
+ const CONFIG_SCHEMA_FILE_NAME = "configuration-schema.json";
71
+ /** The name of the project package file */
72
+ const PACKAGE_JSON_FILE = "package.json";
73
+ /** The name of the app configuration file */
74
+ const APP_CONFIG_FILE = "app.config.yaml";
75
+ /** The name of the install configuration file */
76
+ const INSTALL_YAML_FILE = "install.yaml";
77
+ /** Get the path to the extension point folder */
78
+ function getExtensionPointFolderPath(extensionPointId) {
79
+ return `src/${extensionPointId.replaceAll("/", "-")}`;
80
+ }
81
+
82
+ //#endregion
83
+ //#region source/commands/utils.ts
84
+ /** Format file content using prettier, inferring the parser from the file path. */
85
+ function prettierFormat(content, filepath) {
86
+ return prettier.format(content, {
87
+ semi: true,
88
+ quoteStyle: "double",
89
+ arrowParens: "always",
90
+ bracketSameLine: true,
91
+ bracketSpacing: true,
92
+ trailingComma: "all",
93
+ tabWidth: 2,
94
+ useTabs: false,
95
+ printWidth: 80,
96
+ filepath
97
+ });
98
+ }
99
+ /** Load the app commerce config */
100
+ async function loadAppManifest() {
101
+ const appConfig = await require_config.parseCommerceAppConfig();
102
+ consola.default.debug("Loaded app commerce config");
103
+ return appConfig;
104
+ }
105
+ /**
106
+ * Path to an extension point's `.generated` directory, relative to the project root.
107
+ * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
108
+ */
109
+ function getGeneratedDir(extensionPointId) {
110
+ return (0, path.join)(getExtensionPointFolderPath(extensionPointId), GENERATED_PATH);
111
+ }
112
+ /**
113
+ * Path to an extension point's generated actions directory, relative to the project root.
114
+ * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
115
+ */
116
+ function getActionsDir(extensionPointId) {
117
+ return (0, path.join)(getExtensionPointFolderPath(extensionPointId), GENERATED_ACTIONS_PATH);
118
+ }
119
+ /**
120
+ * Path to a specific generated action file, relative to the project root.
121
+ * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
122
+ * @param actionName - The name of the action, e.g. "my-action"
123
+ */
124
+ function getActionPath(extensionPointId, actionName) {
125
+ return (0, path.join)(getActionsDir(extensionPointId), `${actionName}.js`);
126
+ }
127
+ /**
128
+ * Path to an extension point's `ext.config.yaml`, relative to the project root.
129
+ * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
130
+ */
131
+ function getExtConfigPath(extensionPointId) {
132
+ return (0, path.join)(getExtensionPointFolderPath(extensionPointId), "ext.config.yaml");
133
+ }
134
+ /**
135
+ * Path to an Admin UI SDK generated actions directory, relative to the project root.
136
+ * @param extensionPointId - The extension point ID, e.g. "commerce/backend-ui/1"
137
+ */
138
+ function getAdminUiSdkActionsDir(extensionPointId) {
139
+ return (0, path.join)(getExtensionPointFolderPath(extensionPointId), ADMIN_UI_SDK_ACTIONS_PATH);
140
+ }
141
+ /**
142
+ * Path to the generated Admin UI SDK registration action file, relative to the project root.
143
+ * @param extensionPointId - The extension point ID, e.g. "commerce/backend-ui/1"
144
+ */
145
+ function getAdminUiSdkRegistrationActionPath(extensionPointId) {
146
+ return (0, path.join)(getAdminUiSdkActionsDir(extensionPointId), "index.js");
147
+ }
42
148
 
149
+ //#endregion
150
+ //#region source/commands/generate/actions/config.ts
151
+ /** The list of Commerce variables that are required for the runtime actions */
152
+ const COMMERCE_VARIABLES = [
153
+ "AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
154
+ "AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
155
+ "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID",
156
+ "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
157
+ "AIO_COMMERCE_AUTH_IMS_ORG_ID",
158
+ "AIO_COMMERCE_AUTH_IMS_SCOPES"
159
+ ];
160
+ /** The inputs for the generated runtime actions */
161
+ const COMMERCE_ACTION_INPUTS = Object.fromEntries(COMMERCE_VARIABLES.map((variable) => [variable, `$${variable}`]));
162
+ const CUSTOM_IMPORTS_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_IMPORTS}}";
163
+ const CUSTOM_SCRIPTS_MAP_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_MAP}}";
164
+ const CUSTOM_SCRIPTS_LOADER_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_LOADER}}";
165
+ const REGISTRATION_JSON_PLACEHOLDER = "// {{REGISTRATION_JSON}}";
166
+ /**
167
+ * Creates a runtime action configuration.
168
+ * @param actionName - The name of the action.
169
+ * @param options - Optional configuration options.
170
+ */
171
+ function createActionDefinition(actionName, config = {}, options = {}) {
172
+ const def = {
173
+ ...options,
174
+ function: `${GENERATED_ACTIONS_PATH}/${actionName}.js`,
175
+ web: options.web ?? "yes",
176
+ runtime: "nodejs:22",
177
+ annotations: {
178
+ "require-adobe-auth": true,
179
+ final: true
180
+ }
181
+ };
182
+ if (config.requiresEncryptionKey) def.inputs = {
183
+ ...def.inputs,
184
+ AIO_COMMERCE_CONFIG_ENCRYPTION_KEY: "$AIO_COMMERCE_CONFIG_ENCRYPTION_KEY"
185
+ };
186
+ return def;
187
+ }
188
+ /**
189
+ * Gets the runtime actions to be generated from the ext.config.yaml configuration.
190
+ * @param extConfig - The ext.config.yaml configuration.
191
+ */
192
+ function getRuntimeActions(extConfig, dir) {
193
+ return Object.entries(extConfig.runtimeManifest?.packages?.["app-management"]?.actions ?? {}).map(([name, _]) => ({
194
+ name,
195
+ templateFile: (0, path.join)(dir, `${name}.js.template`)
196
+ }));
197
+ }
198
+ /**
199
+ * Builds the ext.config.yaml configuration for the extensibility extension.
200
+ * @param features - The features that are enabled for the app.
201
+ */
202
+ function buildAppManagementExtConfig(appConfig) {
203
+ const features = require_validate.getConfigDomains(appConfig);
204
+ const hasPasswordFieldsInSchema = require_validate.hasBusinessConfigSchema(appConfig) && appConfig.businessConfig.schema.some((field) => field.type === "password");
205
+ const extConfig = {
206
+ hooks: { "pre-app-build": "EXTENSION=extensibility/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
207
+ operations: { workerProcess: [{
208
+ type: "action",
209
+ impl: `${PACKAGE_NAME}/app-config`
210
+ }] },
211
+ runtimeManifest: { packages: { [PACKAGE_NAME]: {
212
+ license: "Apache-2.0",
213
+ actions: { "app-config": createActionDefinition("app-config") }
214
+ } } }
215
+ };
216
+ if ([
217
+ "installation.customInstallationSteps",
218
+ "eventing.commerce",
219
+ "eventing.external",
220
+ "webhooks",
221
+ "adminUiSdk"
222
+ ].some((feature) => features.has(feature))) {
223
+ extConfig.operations.workerProcess.push({
224
+ type: "action",
225
+ impl: `${PACKAGE_NAME}/installation`
226
+ });
227
+ extConfig.runtimeManifest.packages[PACKAGE_NAME].actions.installation = createActionDefinition("installation", { requiresEncryptionKey: hasPasswordFieldsInSchema }, {
228
+ inputs: {
229
+ ...COMMERCE_ACTION_INPUTS,
230
+ LOG_LEVEL: "$LOG_LEVEL"
231
+ },
232
+ limits: { timeout: 6e5 }
233
+ });
234
+ }
235
+ return extConfig;
236
+ }
237
+ /** Builds the ext.config.yaml configuration for the business configuration extension. */
238
+ function buildBusinessConfigurationExtConfig() {
239
+ const actions = [{
240
+ name: "config",
241
+ templateFile: "config.js.template",
242
+ requiresEncryptionKey: true
243
+ }, {
244
+ name: "scope-tree",
245
+ templateFile: "scope-tree.js.template"
246
+ }];
247
+ return {
248
+ hooks: { "pre-app-build": "EXTENSION=configuration/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
249
+ operations: { workerProcess: actions.map((action) => ({
250
+ type: "action",
251
+ impl: `${PACKAGE_NAME}/${action.name}`
252
+ })) },
253
+ runtimeManifest: { packages: { [PACKAGE_NAME]: {
254
+ license: "Apache-2.0",
255
+ actions: Object.fromEntries(actions.map((action) => [action.name, createActionDefinition(action.name, action)]))
256
+ } } }
257
+ };
258
+ }
259
+ /**
260
+ * Builds the ext.config.yaml configuration for the Admin UI SDK backend-ui extension.
261
+ */
262
+ function buildAdminUiSdkExtConfig() {
263
+ return {
264
+ hooks: { "pre-app-build": "EXTENSION=backend-ui/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
265
+ operations: { view: [{
266
+ type: "web",
267
+ impl: "index.html"
268
+ }] },
269
+ web: "web-src",
270
+ runtimeManifest: { packages: { [ADMIN_UI_SDK_PACKAGE_NAME]: {
271
+ license: "Apache-2.0",
272
+ actions: { registration: {
273
+ function: `${ADMIN_UI_SDK_ACTIONS_PATH}/index.js`,
274
+ web: "yes",
275
+ runtime: "nodejs:22",
276
+ annotations: {
277
+ "require-adobe-auth": true,
278
+ final: true
279
+ }
280
+ } }
281
+ } } }
282
+ };
283
+ }
284
+
285
+ //#endregion
43
286
  //#region ../../packages-private/scripting-utils/source/yaml/helpers.ts
44
287
  /**
45
288
  * Read a YAML file and return a {@link Document}
@@ -49,7 +292,7 @@ async function readYamlFile(path$3) {
49
292
  let doc = new yaml.Document();
50
293
  if ((0, fs.existsSync)(path$3)) try {
51
294
  doc = (0, yaml.parseDocument)(await (0, fs_promises.readFile)(path$3, "utf-8"), { keepSourceTokens: true });
52
- } catch (_) {
295
+ } catch {
53
296
  const file = (0, path.basename)(path$3);
54
297
  throw new Error(`Failed to parse ${file}`);
55
298
  }
@@ -114,16 +357,23 @@ function getOrCreateMap(doc, path$6, options) {
114
357
  */
115
358
  async function createOrUpdateExtConfig(path, config, doc) {
116
359
  const extConfigDoc = doc ?? new yaml.Document({});
360
+ if (config.web !== void 0) buildWeb(extConfigDoc, config.web);
117
361
  config.hooks ??= {};
118
- config.operations ??= { workerProcess: [] };
119
362
  config.runtimeManifest ??= { packages: {} };
120
363
  await buildHooks(extConfigDoc, config.hooks);
121
- buildOperations(extConfigDoc, config.operations);
364
+ if (config.operations !== void 0) buildOperations(extConfigDoc, config.operations);
122
365
  buildRuntimeManifest(extConfigDoc, config.runtimeManifest);
123
366
  await writeExtConfig(path, extConfigDoc);
124
367
  return extConfigDoc;
125
368
  }
126
369
  /**
370
+ * Set the top-level `web` scalar only if it's missing.
371
+ * Preserves any user-customized value already present.
372
+ */
373
+ function buildWeb(extConfig, web) {
374
+ if (!extConfig.hasIn(["web"])) extConfig.add(extConfig.createPair("web", web));
375
+ }
376
+ /**
127
377
  * Build the definition for a runtime action
128
378
  * @param name - The name of the action
129
379
  * @param path - The path where the action is located
@@ -161,20 +411,36 @@ function buildActionDefinition(action) {
161
411
  * @param operations - The operations to build
162
412
  */
163
413
  function buildOperations(extConfig, operations) {
164
- getOrCreateMap(extConfig, ["operations"], { onBeforeCreate: (pair) => {
165
- pair.key.spaceBefore = true;
166
- } });
167
- const workerProcess = getOrCreateSeq(extConfig, ["operations", "workerProcess"], { onBeforeCreate: (pair) => {
168
- pair.key.commentBefore = " These worker processes definitions are auto-generated. Do not remove or manually edit.";
169
- } });
170
- workerProcess.items = [];
171
414
  const ourOps = operations.workerProcess ?? [];
172
- workerProcess.items.push(...ourOps.map((op) => {
173
- const map = new yaml.YAMLMap();
174
- map.set("type", op.type);
175
- map.set("impl", op.impl);
176
- return map;
177
- }));
415
+ if (ourOps.length > 0) {
416
+ getOrCreateMap(extConfig, ["operations"], { onBeforeCreate: (pair) => {
417
+ pair.key.spaceBefore = true;
418
+ } });
419
+ const workerProcess = getOrCreateSeq(extConfig, ["operations", "workerProcess"], { onBeforeCreate: (pair) => {
420
+ pair.key.commentBefore = " These worker processes definitions are auto-generated. Do not remove or manually edit.";
421
+ } });
422
+ workerProcess.items = [];
423
+ workerProcess.items.push(...ourOps.map((op) => {
424
+ const map = new yaml.YAMLMap();
425
+ map.set("type", op.type);
426
+ map.set("impl", op.impl);
427
+ return map;
428
+ }));
429
+ } else if (extConfig.hasIn(["operations", "workerProcess"])) extConfig.deleteIn(["operations", "workerProcess"]);
430
+ if (operations.view !== void 0) {
431
+ getOrCreateMap(extConfig, ["operations"], { onBeforeCreate: (pair) => {
432
+ pair.key.spaceBefore = true;
433
+ } });
434
+ const view = getOrCreateSeq(extConfig, ["operations", "view"]);
435
+ if (view.items.length === 0) view.items.push(...operations.view.map((v) => {
436
+ const map = new yaml.YAMLMap();
437
+ map.set("type", v.type);
438
+ map.set("impl", v.impl);
439
+ return map;
440
+ }));
441
+ }
442
+ const operationsMap = extConfig.getIn(["operations"]);
443
+ if (operationsMap instanceof yaml.YAMLMap && operationsMap.items.length === 0) extConfig.deleteIn(["operations"]);
178
444
  }
179
445
  /**
180
446
  * Build the `runtimeManifest` section of the `ext.config.yaml` file
@@ -238,175 +504,35 @@ async function writeExtConfig(configPath, doc) {
238
504
  }
239
505
 
240
506
  //#endregion
241
- //#region source/commands/constants.ts
242
- /** The name of the package containing all the generated runtime actions */
243
- const PACKAGE_NAME = "app-management";
244
- /** The path to the directory containing the generated files */
245
- const GENERATED_PATH = ".generated";
246
- /** The folder name containing the runtime actions */
247
- const ACTIONS_FOLDER = `actions/${PACKAGE_NAME}`;
248
- /** The path to the directory containing the generated actions */
249
- const GENERATED_ACTIONS_PATH = `${GENERATED_PATH}/${ACTIONS_FOLDER}`;
250
- /** The name of the extension point for extensibility. */
251
- const EXTENSIBILITY_EXTENSION_POINT_ID = "commerce/extensibility/1";
252
- /** The name of the extension point for configuration. */
253
- const CONFIGURATION_EXTENSION_POINT_ID = "commerce/configuration/1";
254
- /** The name of the configuration schema file */
255
- const APP_MANIFEST_FILE = "app.commerce.manifest.json";
256
- /** The name of the commerce app configuration file */
257
- const COMMERCE_APP_CONFIG_FILE = "app.commerce.config";
258
- /** The name of the configuration schema file */
259
- const CONFIG_SCHEMA_FILE_NAME = "configuration-schema.json";
260
- /** The name of the project package file */
261
- const PACKAGE_JSON_FILE = "package.json";
262
- /** The name of the app configuration file */
263
- const APP_CONFIG_FILE = "app.config.yaml";
264
- /** The name of the install configuration file */
265
- const INSTALL_YAML_FILE = "install.yaml";
266
- /** Get the path to the extension point folder */
267
- function getExtensionPointFolderPath(extensionPointId) {
268
- return `src/${extensionPointId.replaceAll("/", "-")}`;
269
- }
270
-
271
- //#endregion
272
- //#region source/commands/utils.ts
273
- /** Load the app commerce config */
274
- async function loadAppManifest() {
275
- const appConfig = await require_config.parseCommerceAppConfig();
276
- consola.default.debug("Loaded app commerce config");
277
- return appConfig;
278
- }
279
-
280
- //#endregion
281
- //#region source/commands/generate/actions/config.ts
282
- /** The list of Commerce variables that are required for the runtime actions */
283
- const COMMERCE_VARIABLES = [
284
- "AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
285
- "AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
286
- "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID",
287
- "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
288
- "AIO_COMMERCE_AUTH_IMS_ORG_ID",
289
- "AIO_COMMERCE_AUTH_IMS_SCOPES"
290
- ];
291
- /** The inputs for the generated runtime actions */
292
- const COMMERCE_ACTION_INPUTS = Object.fromEntries(COMMERCE_VARIABLES.map((variable) => [variable, `$${variable}`]));
293
- const CUSTOM_IMPORTS_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_IMPORTS}}";
294
- const CUSTOM_SCRIPTS_MAP_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_MAP}}";
295
- const CUSTOM_SCRIPTS_LOADER_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_LOADER}}";
296
- /**
297
- * Creates a runtime action configuration.
298
- * @param actionName - The name of the action.
299
- * @param options - Optional configuration options.
300
- */
301
- function createActionDefinition(actionName, config = {}, options = {}) {
302
- const def = {
303
- ...options,
304
- function: `${GENERATED_ACTIONS_PATH}/${actionName}.js`,
305
- web: options.web ?? "yes",
306
- runtime: "nodejs:22",
307
- annotations: {
308
- "require-adobe-auth": true,
309
- final: true
310
- }
311
- };
312
- if (config.requiresEncryptionKey) def.inputs = {
313
- ...def.inputs,
314
- AIO_COMMERCE_CONFIG_ENCRYPTION_KEY: "$AIO_COMMERCE_CONFIG_ENCRYPTION_KEY"
315
- };
316
- return def;
317
- }
318
- /**
319
- * Gets the runtime actions to be generated from the ext.config.yaml configuration.
320
- * @param extConfig - The ext.config.yaml configuration.
321
- */
322
- function getRuntimeActions(extConfig, dir) {
323
- return Object.entries(extConfig.runtimeManifest?.packages?.["app-management"]?.actions ?? {}).map(([name, _]) => ({
324
- name,
325
- templateFile: (0, path.join)(dir, `${name}.js.template`)
326
- }));
327
- }
507
+ //#region source/commands/generate/actions/lib.ts
508
+ const __dirname$1 = (0, path.dirname)((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
509
+ /** The path to the action templates directory, sibling to this file. */
510
+ const TEMPLATES_DIR = (0, path.join)(__dirname$1, "templates");
328
511
  /**
329
- * Builds the ext.config.yaml configuration for the extensibility extension.
330
- * @param features - The features that are enabled for the app.
512
+ * Read an extension point's ext.config.yaml file and return it as a JS object.
513
+ * @param extensionPointId - The extension point ID to read the config for.
331
514
  */
332
- function buildAppManagementExtConfig(appConfig) {
333
- const features = require_validate.getConfigDomains(appConfig);
334
- const hasPasswordFieldsInSchema = require_validate.hasBusinessConfigSchema(appConfig) && appConfig.businessConfig.schema.some((field) => field.type === "password");
335
- const extConfig = {
336
- hooks: { "pre-app-build": "EXTENSION=extensibility/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
337
- operations: { workerProcess: [{
338
- type: "action",
339
- impl: `${PACKAGE_NAME}/app-config`
340
- }] },
341
- runtimeManifest: { packages: { [PACKAGE_NAME]: {
342
- license: "Apache-2.0",
343
- actions: { "app-config": createActionDefinition("app-config") }
344
- } } }
345
- };
346
- if ([
347
- "installation.customInstallationSteps",
348
- "eventing.commerce",
349
- "eventing.external"
350
- ].some((feature) => features.has(feature))) {
351
- extConfig.operations.workerProcess.push({
352
- type: "action",
353
- impl: `${PACKAGE_NAME}/installation`
354
- });
355
- extConfig.runtimeManifest.packages[PACKAGE_NAME].actions.installation = createActionDefinition("installation", { requiresEncryptionKey: hasPasswordFieldsInSchema }, {
356
- inputs: {
357
- ...COMMERCE_ACTION_INPUTS,
358
- LOG_LEVEL: "$LOG_LEVEL"
359
- },
360
- limits: { timeout: 6e5 }
361
- });
362
- }
363
- return extConfig;
364
- }
365
- /** Builds the ext.config.yaml configuration for the business configuration extension. */
366
- function buildBusinessConfigurationExtConfig() {
367
- const actions = [{
368
- name: "config",
369
- templateFile: "config.js.template",
370
- requiresEncryptionKey: true
371
- }, {
372
- name: "scope-tree",
373
- templateFile: "scope-tree.js.template"
374
- }];
375
- return {
376
- hooks: { "pre-app-build": "EXTENSION=configuration/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
377
- operations: { workerProcess: actions.map((action) => ({
378
- type: "action",
379
- impl: `${PACKAGE_NAME}/${action.name}`
380
- })) },
381
- runtimeManifest: { packages: { [PACKAGE_NAME]: {
382
- license: "Apache-2.0",
383
- actions: Object.fromEntries(actions.map((action) => [action.name, createActionDefinition(action.name, action)]))
384
- } } }
385
- };
386
- }
387
-
388
- //#endregion
389
- //#region source/commands/generate/actions/main.ts
390
- const __dirname$1 = (0, path.dirname)((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
391
- async function run$4(appManifest) {
392
- await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, EXTENSIBILITY_EXTENSION_POINT_ID), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID);
393
- if (require_validate.hasBusinessConfigSchema(appManifest)) await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, CONFIGURATION_EXTENSION_POINT_ID), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID);
394
- }
395
- /** Run the generate actions command */
396
- async function exec$5() {
515
+ async function readExtConfig(extensionPointId) {
516
+ const extConfigPath = (0, path.join)(await require_config.getProjectRootDirectory(), getExtConfigPath(extensionPointId));
397
517
  try {
398
- await run$4(await loadAppManifest());
518
+ return {
519
+ path: extConfigPath,
520
+ doc: await readYamlFile(extConfigPath)
521
+ };
399
522
  } catch (error) {
400
- if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
401
- consola.consola.error(error);
402
- process.exit(1);
523
+ const reason = error instanceof Error ? error.message : "Unexpected error while reading the file.";
524
+ throw new Error([
525
+ `Could not read ext.config.yaml for ${extensionPointId} at ${extConfigPath}.`,
526
+ `Make sure the file exists and is valid YAML. You can regenerate it with "aio-commerce-lib-app generate actions".`,
527
+ `Reason: ${reason}`
528
+ ].join("\n"));
403
529
  }
404
530
  }
405
531
  /** Update the ext.config.yaml file */
406
532
  async function updateExtConfig(appConfig, extensionPointId) {
407
533
  consola.consola.info(`Updating ext.config.yaml for ${extensionPointId}...`);
408
- const extConfigPath = (0, path.join)(await require_config.makeOutputDirFor(getExtensionPointFolderPath(extensionPointId)), "ext.config.yaml");
409
- const extConfigDoc = await readYamlFile(extConfigPath);
534
+ await require_config.makeOutputDirFor(getExtensionPointFolderPath(extensionPointId));
535
+ const { path: extConfigPath, doc: extConfigDoc } = await readExtConfig(extensionPointId);
410
536
  let extConfig;
411
537
  switch (extensionPointId) {
412
538
  case EXTENSIBILITY_EXTENSION_POINT_ID:
@@ -415,24 +541,26 @@ async function updateExtConfig(appConfig, extensionPointId) {
415
541
  case CONFIGURATION_EXTENSION_POINT_ID:
416
542
  extConfig = buildBusinessConfigurationExtConfig();
417
543
  break;
418
- default: throw new Error(`Unsupported extension point ID: ${extensionPointId}`);
544
+ case BACKEND_UI_EXTENSION_POINT_ID:
545
+ extConfig = buildAdminUiSdkExtConfig();
546
+ break;
419
547
  }
420
548
  await createOrUpdateExtConfig(extConfigPath, extConfig, extConfigDoc);
421
549
  return extConfig;
422
550
  }
423
551
  /** Generate the action files */
424
- async function generateActionFiles(appManifest, actions, extensionPointId) {
552
+ async function generateActionFiles(appManifest, actions, extensionPointId, templatesDir) {
425
553
  consola.consola.start("Generating runtime actions...");
426
- const outputDir = await require_config.makeOutputDirFor((0, path.join)(getExtensionPointFolderPath(extensionPointId), GENERATED_ACTIONS_PATH));
554
+ await require_config.makeOutputDirFor(getActionsDir(extensionPointId));
555
+ const projectRoot = await require_config.getProjectRootDirectory();
427
556
  const outputFiles = [];
428
- const templatesDir = (0, path.join)(__dirname$1, "generate/actions/templates");
429
557
  for (const action of actions) {
430
558
  let template = await (0, fs_promises.readFile)((0, path.join)(templatesDir, action.templateFile), "utf-8");
431
559
  if (action.name === "installation") {
432
560
  const scriptsTemplate = await generateCustomScriptsTemplate(await (0, fs_promises.readFile)((0, path.join)(templatesDir, "app-management", "custom-scripts.js.template"), "utf-8"), appManifest);
433
561
  template = applyCustomScripts(template, scriptsTemplate);
434
562
  }
435
- const actionPath = (0, path.join)(outputDir, `${action.name}.js`);
563
+ const actionPath = (0, path.join)(projectRoot, getActionPath(extensionPointId, action.name));
436
564
  await (0, fs_promises.writeFile)(actionPath, template, "utf-8");
437
565
  outputFiles.push(` ${(0, path.relative)(process.cwd(), actionPath)}`);
438
566
  }
@@ -455,7 +583,7 @@ function applyCustomScripts(installationTemplate, customScriptsTemplate) {
455
583
  async function generateCustomScriptsTemplate(template, appManifest) {
456
584
  if (!require_webhooks.hasCustomInstallationSteps(appManifest)) return null;
457
585
  const projectRoot = await require_config.getProjectRootDirectory();
458
- const installationActionDir = (0, path.join)(projectRoot, getExtensionPointFolderPath(EXTENSIBILITY_EXTENSION_POINT_ID), GENERATED_ACTIONS_PATH);
586
+ const installationActionDir = (0, path.join)(projectRoot, getActionsDir(EXTENSIBILITY_EXTENSION_POINT_ID));
459
587
  const customSteps = appManifest.installation.customInstallationSteps;
460
588
  const importStatements = customSteps.map((step, index) => {
461
589
  let relativeImportPath = (0, path.relative)(installationActionDir, (0, path.join)(projectRoot, step.script));
@@ -469,36 +597,92 @@ async function generateCustomScriptsTemplate(template, appManifest) {
469
597
  }).join("\n");
470
598
  return template.replace(CUSTOM_IMPORTS_PLACEHOLDER, importStatements).replace(CUSTOM_SCRIPTS_MAP_PLACEHOLDER, scriptMap);
471
599
  }
600
+ /**
601
+ * Generates `registration/index.js` with the Admin UI SDK registration config
602
+ * inlined as a JS object literal.
603
+ * @param appManifest - The validated app config; must satisfy `hasAdminUiSdk`.
604
+ * @param extensionPointId - The extension point ID that owns the registration action.
605
+ * @param templatesDir - The root directory containing the action templates.
606
+ */
607
+ async function generateRegistrationActionFile(appManifest, extensionPointId, templatesDir = TEMPLATES_DIR) {
608
+ consola.consola.start("Generating Admin UI SDK registration action...");
609
+ await require_config.makeOutputDirFor(getAdminUiSdkActionsDir(extensionPointId));
610
+ const projectRoot = await require_config.getProjectRootDirectory();
611
+ const template = await (0, fs_promises.readFile)((0, path.join)(templatesDir, "admin-ui-sdk", "registration.js.template"), "utf-8");
612
+ const { registration } = appManifest.adminUiSdk;
613
+ const actionPath = (0, path.join)(projectRoot, getAdminUiSdkRegistrationActionPath(extensionPointId));
614
+ await (0, fs_promises.writeFile)(actionPath, await prettierFormat(template.replace(REGISTRATION_JSON_PLACEHOLDER, `const registration = ${(0, safe_stable_stringify.default)(registration)};`), actionPath), "utf-8");
615
+ consola.consola.success(`Generated registration action at ${(0, path.relative)(process.cwd(), actionPath)}`);
616
+ }
617
+
618
+ //#endregion
619
+ //#region source/commands/generate/actions/main.ts
620
+ /**
621
+ * Runs the generate actions command for the given extension point.
622
+ * @param appManifest - The app manifest, used to determine which actions to generate and their content.
623
+ * @param templatesDir - The directory to load templates from, for testing purposes. Defaults to the generated actions template root.
624
+ */
625
+ async function run$5(appManifest, templatesDir = TEMPLATES_DIR) {
626
+ await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, EXTENSIBILITY_EXTENSION_POINT_ID), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID, templatesDir);
627
+ if (require_validate.hasBusinessConfigSchema(appManifest)) await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, CONFIGURATION_EXTENSION_POINT_ID), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID, templatesDir);
628
+ if (require_webhooks.hasAdminUiSdk(appManifest)) {
629
+ await updateExtConfig(appManifest, BACKEND_UI_EXTENSION_POINT_ID);
630
+ await generateRegistrationActionFile(appManifest, BACKEND_UI_EXTENSION_POINT_ID, templatesDir);
631
+ }
632
+ }
633
+ /** Run the generate actions command */
634
+ async function exec$5() {
635
+ try {
636
+ await run$5(await loadAppManifest());
637
+ } catch (error) {
638
+ if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
639
+ else consola.consola.error(error);
640
+ process.exit(1);
641
+ }
642
+ }
472
643
 
473
644
  //#endregion
474
645
  //#region source/commands/generate/manifest/main.ts
475
- async function run$3(appConfig) {
646
+ async function run$4(appConfig) {
476
647
  consola.consola.info("Generating app manifest...");
477
648
  const contents = (0, safe_stable_stringify.stringify)(appConfig, null, 2);
478
- await (0, fs_promises.writeFile)((0, path.join)(await require_config.makeOutputDirFor(`${getExtensionPointFolderPath(EXTENSIBILITY_EXTENSION_POINT_ID)}/.generated`), APP_MANIFEST_FILE), contents, "utf-8");
649
+ await (0, fs_promises.writeFile)((0, path.join)(await require_config.makeOutputDirFor(getGeneratedDir(EXTENSIBILITY_EXTENSION_POINT_ID)), APP_MANIFEST_FILE), contents, "utf-8");
479
650
  consola.consola.success(`Generated ${APP_MANIFEST_FILE}`);
480
651
  }
481
652
  /** Run the generate manifest command */
482
653
  async function exec$4() {
483
654
  try {
484
- await run$3(await loadAppManifest());
655
+ await run$4(await loadAppManifest());
485
656
  } catch (error) {
486
657
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
487
- consola.consola.error(error);
658
+ else consola.consola.error(error);
488
659
  process.exit(1);
489
660
  }
490
661
  }
491
662
 
663
+ //#endregion
664
+ //#region ../../packages-private/scripting-utils/source/filesystem/helpers.ts
665
+ /**
666
+ * Touch a file, creating it if it doesn't exist.
667
+ * @param filePath The path of the file to touch
668
+ */
669
+ async function touch(filePath) {
670
+ await (0, fs_promises.writeFile)(filePath, "", { flag: "wx" }).catch((e) => {
671
+ if (e.code !== "EEXIST") throw e;
672
+ });
673
+ }
674
+
492
675
  //#endregion
493
676
  //#region source/commands/generate/schema/main.ts
494
- async function run$2(appConfig) {
495
- consola.consola.info("Generating configuration schema...");
677
+ async function run$3(appConfig) {
496
678
  if (!require_validate.hasBusinessConfigSchema(appConfig)) {
497
- consola.consola.warn("Business configuration schema not found");
679
+ consola.consola.debug("Business configuration schema not found in application configuration. Nothing to do.");
498
680
  return;
499
681
  }
500
- const projectDir = (0, path.dirname)(await require_config.findNearestPackageJson() ?? process.cwd());
682
+ consola.consola.info("Generating configuration schema...");
683
+ const projectDir = await require_config.getProjectRootDirectory();
501
684
  const envPath = (0, path.join)(projectDir, ".env");
685
+ await touch(envPath);
502
686
  process.loadEnvFile(envPath);
503
687
  if (appConfig.businessConfig.schema.some((field) => field.type === "password")) {
504
688
  const packageExec = require_config.getExecCommand(await require_config.detectPackageManager(projectDir));
@@ -506,35 +690,35 @@ async function run$2(appConfig) {
506
690
  else (0, child_process.execSync)(`${packageExec} aio-commerce-lib-config encryption setup`);
507
691
  }
508
692
  const contents = (0, safe_stable_stringify.stringify)(appConfig.businessConfig.schema, null, 2);
509
- await (0, fs_promises.writeFile)((0, path.join)(await require_config.makeOutputDirFor(`${getExtensionPointFolderPath(CONFIGURATION_EXTENSION_POINT_ID)}/.generated`), CONFIG_SCHEMA_FILE_NAME), contents, "utf-8");
693
+ await (0, fs_promises.writeFile)((0, path.join)(await require_config.makeOutputDirFor(getGeneratedDir(CONFIGURATION_EXTENSION_POINT_ID)), CONFIG_SCHEMA_FILE_NAME), contents, "utf-8");
510
694
  consola.consola.success(`Generated ${CONFIG_SCHEMA_FILE_NAME}`);
511
695
  }
512
- /** Run the generate manifest command */
696
+ /** Run the generate schema command */
513
697
  async function exec$3() {
514
698
  try {
515
- await run$2(await loadAppManifest());
699
+ await run$3(await loadAppManifest());
516
700
  } catch (error) {
517
701
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
518
- consola.consola.error(error);
702
+ else consola.consola.error(error);
519
703
  process.exit(1);
520
704
  }
521
705
  }
522
706
 
523
707
  //#endregion
524
708
  //#region source/commands/hooks/postinstall.ts
525
- async function run$1(appManifest) {
709
+ async function run$2(appManifest, templatesDir) {
710
+ await run$5(appManifest, templatesDir);
526
711
  await run$4(appManifest);
527
712
  await run$3(appManifest);
528
- await run$2(appManifest);
529
713
  }
530
714
  /** Runs the postinstall hook */
531
715
  async function exec$2() {
532
716
  consola.default.debug("Running lib-app postinstall hook");
533
717
  try {
534
- await run$1(await loadAppManifest());
718
+ await run$2(await loadAppManifest());
535
719
  } catch (error) {
536
720
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.default.error(error.display());
537
- consola.default.error(error);
721
+ else consola.default.error(error);
538
722
  process.exit(1);
539
723
  }
540
724
  }
@@ -590,19 +774,27 @@ function resolveImsS2SContext() {
590
774
  }
591
775
  /**
592
776
  * Syncs the IMS credentials environment variables from the configured IMS context in
593
- * the .env file, in a way that is compatible with `@adobe/aio-commerce-lib-auth`
777
+ * the .env file, in a way that is compatible with `@adobe/aio-commerce-lib-auth`.
594
778
  */
595
779
  async function syncImsCredentials() {
596
780
  const envPath = resolveEnvPath();
781
+ if (!(0, fs.existsSync)(envPath)) return {
782
+ ok: false,
783
+ reason: "missing-env"
784
+ };
597
785
  const envVars = dotenv.default.parse((0, fs.readFileSync)(envPath, "utf8"));
598
786
  const imsContext = await resolveImsS2SContext();
599
- if (!imsContext) return;
787
+ if (!imsContext) return {
788
+ ok: false,
789
+ reason: "no-ims-context"
790
+ };
600
791
  const { data } = imsContext;
601
792
  for (const [key, value] of Object.entries(data)) {
602
793
  const oauthKey = IMS_KEYS[key];
603
794
  if (!oauthKey) continue;
604
795
  if (!envVars[oauthKey] || envVars[oauthKey] !== value) replaceEnvVar(envPath, oauthKey, value);
605
796
  }
797
+ return { ok: true };
606
798
  }
607
799
 
608
800
  //#endregion
@@ -610,17 +802,26 @@ async function syncImsCredentials() {
610
802
  /**
611
803
  * Runs the pre-app-build hook for the given extension.
612
804
  * @param extension - The extension to run the hook for.
805
+ * @param templatesDir - The directory to load templates from, for testing purposes. Defaults to the generated actions template root.
613
806
  */
614
- async function run(extension) {
807
+ async function run$1(extension, templatesDir = TEMPLATES_DIR) {
615
808
  const appManifest = await loadAppManifest();
616
809
  if (extension === "extensibility/1") {
617
- await run$3(appManifest);
810
+ const { doc: extensibilityExtConfig } = await readExtConfig(EXTENSIBILITY_EXTENSION_POINT_ID);
811
+ await run$4(appManifest);
812
+ await generateActionFiles(appManifest, getRuntimeActions(extensibilityExtConfig.toJS(), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID, templatesDir);
618
813
  consola.default.info("Syncing IMS credentials...");
619
814
  await syncImsCredentials();
620
815
  return;
621
816
  }
622
817
  if (extension === "configuration/1") {
623
- await run$2(appManifest);
818
+ const { doc: businessConfigExtConfig } = await readExtConfig(CONFIGURATION_EXTENSION_POINT_ID);
819
+ await run$3(appManifest);
820
+ await generateActionFiles(appManifest, getRuntimeActions(businessConfigExtConfig.toJS(), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID, templatesDir);
821
+ return;
822
+ }
823
+ if (extension === "backend-ui/1") {
824
+ if (require_webhooks.hasAdminUiSdk(appManifest)) await generateRegistrationActionFile(appManifest, BACKEND_UI_EXTENSION_POINT_ID);
624
825
  return;
625
826
  }
626
827
  throw new Error(`Unsupported extension: ${extension}`);
@@ -631,10 +832,10 @@ async function exec$1() {
631
832
  const extension = process.env.EXTENSION;
632
833
  try {
633
834
  if (!extension) throw new Error("EXTENSION environment variable is not set");
634
- await run(extension);
835
+ await run$1(extension);
635
836
  } catch (error) {
636
837
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.default.error(error.display());
637
- consola.default.error(error);
838
+ else consola.default.error(error);
638
839
  process.exit(1);
639
840
  }
640
841
  }
@@ -669,7 +870,7 @@ const DOMAIN_DEFAULTS = {
669
870
  },
670
871
  events: [{
671
872
  name: "plugin.sample_event",
672
- fields: [],
873
+ fields: [{ name: "*" }],
673
874
  label: "Sample Event",
674
875
  description: "Use case description for the event.",
675
876
  runtimeActions: ["my-package/handle-sample-event"]
@@ -691,12 +892,50 @@ const DOMAIN_DEFAULTS = {
691
892
  name: "sample-step",
692
893
  description: "Use case description for the step.",
693
894
  script: "./path/to/script.js"
895
+ }],
896
+ webhooks: [{
897
+ label: "Sample Webhook",
898
+ description: "A sample Commerce Webhook handler.",
899
+ runtimeAction: "my-package/handle-webhook",
900
+ category: "modification",
901
+ webhook: {
902
+ webhook_method: "plugin.sample.event",
903
+ webhook_type: "after",
904
+ batch_name: "my_app",
905
+ hook_name: "sample_hook",
906
+ method: "POST"
907
+ }
908
+ }, {
909
+ label: "Sample Webhook with URL",
910
+ description: "A sample Commerce Webhook handler using an explicit URL.",
911
+ category: "modification",
912
+ webhook: {
913
+ webhook_method: "plugin.sample.event",
914
+ webhook_type: "after",
915
+ batch_name: "my_app",
916
+ hook_name: "sample_hook_url",
917
+ method: "POST",
918
+ url: "https://example.com/webhook"
919
+ }
694
920
  }]
695
921
  };
696
922
 
697
923
  //#endregion
698
924
  //#region source/commands/init/utils.ts
699
- /** Prompt the user to scaffold a new Commerce App configuration. */
925
+ /**
926
+ * Convert the given init options to answers for scaffolding the app config. This is useful for non-interactive use cases, such as running the command with a config file or from another script.
927
+ * @param flags - The initialization options
928
+ */
929
+ function initFlagsToScaffoldAppAnswers(flags) {
930
+ const configFile = `${COMMERCE_APP_CONFIG_FILE}.${flags.configFormat}`;
931
+ return {
932
+ appName: flags.appName,
933
+ configFile,
934
+ configFormat: flags.configFormat,
935
+ domains: new Set(flags.domains)
936
+ };
937
+ }
938
+ /** Prompt the user to scaffold a new Commerce App configuration. When options are provided, prompts are skipped. */
700
939
  async function promptForCommerceAppConfig() {
701
940
  const configFormat = await consola.default.prompt("What format do you want to use for the config file?", {
702
941
  type: "select",
@@ -732,6 +971,10 @@ async function promptForCommerceAppConfig() {
732
971
  label: "Commerce Events",
733
972
  value: "eventing.commerce"
734
973
  },
974
+ {
975
+ label: "Commerce Webhooks",
976
+ value: "webhooks"
977
+ },
735
978
  {
736
979
  label: "External Events",
737
980
  value: "eventing.external"
@@ -742,10 +985,13 @@ async function promptForCommerceAppConfig() {
742
985
  }
743
986
  ]
744
987
  });
988
+ const configFile = `${COMMERCE_APP_CONFIG_FILE}.${configFormat}`;
989
+ const domains = new Set(featuresToAdd);
990
+ consola.default.log("");
745
991
  return {
746
992
  appName,
747
- configFile: `${COMMERCE_APP_CONFIG_FILE}.${configFormat}`,
748
- domains: new Set(featuresToAdd),
993
+ configFile,
994
+ domains,
749
995
  configFormat
750
996
  };
751
997
  }
@@ -770,6 +1016,7 @@ async function getDefaultCommerceAppConfig(cwd, { appName, configFormat, domains
770
1016
  external: DOMAIN_DEFAULTS["eventing.external"]
771
1017
  };
772
1018
  if (domains.has("installation.customInstallationSteps")) defaultConfig.installation = { customInstallationSteps: DOMAIN_DEFAULTS["installation.customInstallationSteps"] };
1019
+ if (domains.has("webhooks")) defaultConfig.webhooks = DOMAIN_DEFAULTS.webhooks;
773
1020
  return [
774
1021
  importStatement,
775
1022
  "",
@@ -784,9 +1031,8 @@ async function getDefaultCommerceAppConfig(cwd, { appName, configFormat, domains
784
1031
  */
785
1032
  async function addExtensionPointToAppConfig(extensionPointId, rootDirectory, commentBefore) {
786
1033
  const appConfigPath = (0, path.join)(rootDirectory, APP_CONFIG_FILE);
787
- const extensionPointFolderPath = (0, path.join)(rootDirectory, getExtensionPointFolderPath(extensionPointId));
788
1034
  let doc;
789
- const includePath = (0, path.relative)(rootDirectory, (0, path.join)(extensionPointFolderPath, "ext.config.yaml"));
1035
+ const includePath = (0, path.relative)(rootDirectory, (0, path.join)(rootDirectory, getExtConfigPath(extensionPointId)));
790
1036
  try {
791
1037
  doc = await readYamlFile(appConfigPath);
792
1038
  } catch (error) {
@@ -844,12 +1090,12 @@ async function addExtensionPointToInstallYaml(extensionPointId, rootDirectory, c
844
1090
 
845
1091
  //#endregion
846
1092
  //#region source/commands/init/lib.ts
847
- /** Ensure app.commerce.config file exists, allow creating if it doesn't */
848
- async function ensureCommerceAppConfig(cwd = process.cwd()) {
1093
+ /** Ensure app.commerce.config file exists, allow creating if it doesn't. When options are provided, prompts are skipped. */
1094
+ async function ensureCommerceAppConfig(cwd = process.cwd(), formatConfig = true, flags) {
849
1095
  let config = null;
850
1096
  try {
851
1097
  config = await require_config.readCommerceAppConfig(cwd);
852
- } catch (_) {}
1098
+ } catch {}
853
1099
  if (config) try {
854
1100
  const validatedConfig = require_validate.validateCommerceAppConfig(config);
855
1101
  consola.consola.success(`${COMMERCE_APP_CONFIG_FILE} found and is valid. Continuing...`);
@@ -860,119 +1106,126 @@ async function ensureCommerceAppConfig(cwd = process.cwd()) {
860
1106
  } catch (error) {
861
1107
  throw new Error(`${COMMERCE_APP_CONFIG_FILE} is invalid`, { cause: error });
862
1108
  }
863
- consola.consola.warn(`${COMMERCE_APP_CONFIG_FILE} not found.`);
864
- if (!await consola.consola.prompt(`Do you want to create a ${"app.commerce.config"} file? (y/n)`, {
865
- type: "confirm",
866
- initial: true,
867
- default: false
868
- })) throw new Error("Initialization cancelled.");
869
- const answers = await promptForCommerceAppConfig();
1109
+ if (!flags) {
1110
+ consola.consola.warn(`${COMMERCE_APP_CONFIG_FILE} not found.`);
1111
+ if (!await consola.consola.prompt(`Do you want to create a ${"app.commerce.config"} file? (y/n)`, {
1112
+ type: "confirm",
1113
+ initial: true,
1114
+ default: false
1115
+ })) throw new Error("Initialization cancelled.");
1116
+ }
1117
+ const answers = flags ? initFlagsToScaffoldAppAnswers(flags) : await promptForCommerceAppConfig();
870
1118
  try {
871
1119
  const configContent = await getDefaultCommerceAppConfig(cwd, answers);
872
- consola.consola.info(`Creating ${answers.configFile}...`);
873
1120
  const path$2 = (0, path.join)(await require_config.getProjectRootDirectory(cwd), answers.configFile);
874
- await (0, fs_promises.writeFile)(path$2, await prettier.format(configContent, {
875
- semi: true,
876
- quoteStyle: "double",
877
- arrowParens: "always",
878
- bracketSameLine: true,
879
- bracketSpacing: true,
880
- trailingComma: "all",
881
- tabWidth: 2,
882
- useTabs: false,
883
- printWidth: 80,
884
- filepath: path$2
885
- }), "utf-8");
1121
+ consola.consola.info(`Creating ${answers.configFile}...`);
1122
+ if (formatConfig) await (0, fs_promises.writeFile)(path$2, await prettierFormat(configContent, path$2), "utf-8");
1123
+ else await (0, fs_promises.writeFile)(path$2, configContent, "utf-8");
1124
+ const config = await require_config.parseCommerceAppConfig(cwd);
886
1125
  consola.consola.success(`Created ${answers.configFile}`);
887
1126
  return {
888
- config: await require_config.parseCommerceAppConfig(cwd),
1127
+ config,
889
1128
  domains: answers.domains
890
1129
  };
891
1130
  } catch (error) {
892
1131
  throw new Error(`Failed to create ${answers.configFile}`, { cause: error });
893
1132
  }
894
1133
  }
895
- /** Ensure package.json has the postinstall script */
1134
+ /**
1135
+ * Ensure a package.json exists and detect the package manager.
1136
+ * @param cwd - Directory to check; defaults to `process.cwd()`
1137
+ */
896
1138
  async function ensurePackageJson(cwd = process.cwd()) {
897
- const packageJson = await require_config.readPackageJson(cwd);
898
- if (!packageJson) {
1139
+ const existing = await require_config.readPackageJson(cwd);
1140
+ let packageJson;
1141
+ if (existing) packageJson = existing;
1142
+ else {
899
1143
  consola.consola.warn("package.json not found. Creating one...");
900
- const packageJsonContent = {
1144
+ packageJson = {
901
1145
  name: "my-commerce-app",
902
1146
  version: "1.0.0",
903
- private: true,
904
- scripts: { postinstall: "npx aio-commerce-lib-app hooks postinstall" }
1147
+ private: true
905
1148
  };
906
- await (0, fs_promises.writeFile)((0, path.join)((0, path.resolve)(cwd), PACKAGE_JSON_FILE), JSON.stringify(packageJsonContent, null, 2), "utf-8");
1149
+ await (0, fs_promises.writeFile)((0, path.join)((0, path.resolve)(cwd), PACKAGE_JSON_FILE), JSON.stringify(packageJson, null, 2), "utf-8");
907
1150
  consola.consola.success("Wrote package.json");
908
- return {
909
- packageJson: packageJsonContent,
910
- packageManager: "npm",
911
- execCommand: "npx"
912
- };
913
1151
  }
914
- const packageManager = await require_config.detectPackageManager();
1152
+ const packageManager = await require_config.detectPackageManager(cwd);
915
1153
  const execCommand = require_config.getExecCommand(packageManager);
916
- const postinstallScript = `${execCommand} aio-commerce-lib-app hooks postinstall`;
917
- packageJson.scripts ??= {};
918
- if (packageJson.scripts.postinstall === postinstallScript || packageJson.scripts.postinstall?.includes(postinstallScript)) {
919
- consola.consola.success(`postinstall script already configured in ${PACKAGE_JSON_FILE}`);
920
- return {
921
- packageJson,
922
- packageManager,
923
- execCommand
924
- };
925
- }
926
- if (packageJson.scripts.postinstall) {
927
- consola.consola.warn(`${PACKAGE_JSON_FILE} already has a postinstall script. Adding a new one...`);
928
- (0, child_process.execSync)(`npm pkg set scripts.postinstall="${`${packageJson.scripts.postinstall} && ${postinstallScript}`}"`);
929
- } else {
930
- consola.consola.info(`Adding postinstall script to ${PACKAGE_JSON_FILE}...`);
931
- (0, child_process.execSync)(`npm pkg set scripts.postinstall="${postinstallScript}"`);
932
- }
933
- consola.consola.success(`Added postinstall script to ${PACKAGE_JSON_FILE}`);
934
1154
  return {
935
1155
  packageJson,
936
1156
  packageManager,
937
1157
  execCommand
938
1158
  };
939
1159
  }
1160
+ /**
1161
+ * Register the `hooks postinstall` script in package.json.
1162
+ * @param execCommand - Prefix for running local binaries (e.g. `pnpm exec`, `npx`)
1163
+ * @param cwd - Directory containing the package.json to update
1164
+ */
1165
+ async function writePostinstallHook(execCommand, cwd = process.cwd()) {
1166
+ const postinstallScript = `${execCommand} aio-commerce-lib-app hooks postinstall`;
1167
+ const pkg = await _npmcli_package_json.default.load(cwd);
1168
+ const existing = pkg.content.scripts?.postinstall;
1169
+ if (existing === postinstallScript || existing?.includes(postinstallScript)) {
1170
+ consola.consola.success(`postinstall script already configured in ${PACKAGE_JSON_FILE}`);
1171
+ return;
1172
+ }
1173
+ const nextPostinstall = existing ? `${existing} && ${postinstallScript}` : postinstallScript;
1174
+ if (existing) consola.consola.warn(`${PACKAGE_JSON_FILE} already has a postinstall script. Adding a new one...`);
1175
+ else consola.consola.info(`Adding postinstall script to ${PACKAGE_JSON_FILE}...`);
1176
+ pkg.update({ scripts: {
1177
+ ...pkg.content.scripts,
1178
+ postinstall: nextPostinstall
1179
+ } });
1180
+ await pkg.save();
1181
+ consola.consola.success(`Added postinstall script to ${PACKAGE_JSON_FILE}`);
1182
+ }
940
1183
  /** Ensure app.config.yaml has the extension reference */
941
1184
  async function ensureAppConfig(domains, cwd = process.cwd()) {
942
1185
  const rootDirectory = await require_config.getProjectRootDirectory(cwd);
943
1186
  if (domains.has("businessConfig.schema")) await addExtensionPointToAppConfig(CONFIGURATION_EXTENSION_POINT_ID, rootDirectory, " This extension is required for business configuration. Do not remove.");
1187
+ if (domains.has("adminUiSdk")) await addExtensionPointToAppConfig(BACKEND_UI_EXTENSION_POINT_ID, rootDirectory, " This extension is required for Admin UI SDK. Do not remove.");
944
1188
  await addExtensionPointToAppConfig(EXTENSIBILITY_EXTENSION_POINT_ID, rootDirectory, " This extension is required for app management. Do not remove.");
945
1189
  }
946
- /** Install the given dependencies */
1190
+ /**
1191
+ * Install the given dependencies.
1192
+ * @param packageManager - The detected package manager
1193
+ * @param dependencies - Package specifiers to install; no-op when empty
1194
+ * @param cwd - Working directory for the install command
1195
+ */
947
1196
  function runInstall(packageManager, dependencies, cwd = process.cwd()) {
1197
+ if (dependencies.length === 0) return;
948
1198
  const dependencyListString = dependencies.map((dependency) => ` - ${dependency}`).join("\n");
949
- consola.consola.info(`Installing the following dependencies with ${packageManager}:\n${dependencyListString}`);
950
- const packagesToInstall = dependencies.join(" ");
951
- const installCommand = {
952
- pnpm: `pnpm add ${packagesToInstall}`,
953
- yarn: `yarn add ${packagesToInstall}`,
954
- bun: `bun add ${packagesToInstall}`,
955
- npm: `npm install ${packagesToInstall}`
956
- }[packageManager];
957
- try {
958
- (0, child_process.execSync)(installCommand, {
959
- cwd,
960
- stdio: "inherit"
961
- });
962
- consola.consola.success("Dependencies installed successfully");
963
- } catch (error) {
964
- throw new Error(`Failed to install dependencies automatically. Please install manually: ${installCommand}`, { cause: error });
965
- }
1199
+ consola.consola.start([`Installing the following dependencies with ${packageManager}:\n${dependencyListString}\n`, "This may take a few seconds...\n"].join("\n"));
1200
+ const { command, args } = require_config.getInstallCommand(packageManager, dependencies);
1201
+ const displayCommand = [command, ...args].join(" ");
1202
+ const result = (0, child_process.spawnSync)(command, args, {
1203
+ cwd,
1204
+ stdio: "inherit"
1205
+ });
1206
+ if (result.error || result.status !== 0) throw new Error(`Failed to install dependencies automatically. Please install manually: ${displayCommand}`, { cause: result.error ?? /* @__PURE__ */ new Error(`Install exited with code ${result.status}`) });
1207
+ consola.consola.log("");
1208
+ consola.consola.success("Dependencies installed successfully");
966
1209
  }
967
- /** Install required dependencies */
1210
+ /**
1211
+ * Install the domain-specific dependencies derived from the selected domains.
1212
+ * @param packageManager - The detected package manager
1213
+ * @param domains - Domains enabled in the commerce app config
1214
+ * @param cwd - Working directory for the install command
1215
+ */
968
1216
  function installDependencies(packageManager, domains, cwd = process.cwd()) {
969
1217
  const packages = [];
970
- if (domains.has("businessConfig.schema")) packages.push("@adobe/aio-commerce-lib-config");
1218
+ if (domains.has("businessConfig.schema")) packages.push(`@adobe/aio-commerce-lib-config@^1.3.0`);
1219
+ if (packages.length === 0) {
1220
+ consola.consola.info("No additional domain dependencies to install.");
1221
+ return;
1222
+ }
971
1223
  runInstall(packageManager, packages, cwd);
972
1224
  }
973
1225
  /** Run the generation command */
974
1226
  async function runGeneration(appConfig, execCommand) {
975
1227
  try {
1228
+ await run$5(appConfig);
976
1229
  await run$4(appConfig);
977
1230
  await run$3(appConfig);
978
1231
  } catch (error) {
@@ -983,35 +1236,45 @@ async function runGeneration(appConfig, execCommand) {
983
1236
  async function ensureInstallYaml(domains, cwd = process.cwd()) {
984
1237
  const rootDirectory = await require_config.getProjectRootDirectory(cwd);
985
1238
  if (domains.has("businessConfig.schema")) await addExtensionPointToInstallYaml(CONFIGURATION_EXTENSION_POINT_ID, rootDirectory, " This extension is required for business configuration. Do not remove.");
1239
+ if (domains.has("adminUiSdk")) await addExtensionPointToInstallYaml(BACKEND_UI_EXTENSION_POINT_ID, rootDirectory, " This extension is required for Admin UI SDK. Do not remove.");
986
1240
  await addExtensionPointToInstallYaml(EXTENSIBILITY_EXTENSION_POINT_ID, rootDirectory, " This extension is required for app management. Do not remove.");
987
1241
  }
988
1242
 
989
1243
  //#endregion
990
1244
  //#region source/commands/init/main.ts
991
- const REQUIRED_DEPENDENCIES = ["@adobe/aio-commerce-lib-app", "@adobe/aio-commerce-sdk"];
992
- /** Initialize the project with @adobe/aio-commerce-lib-config */
1245
+ const REQUIRED_DEPENDENCIES = ["@adobe/aio-commerce-sdk", `@adobe/aio-commerce-lib-app@1.4.0`];
1246
+ /** Initialize the project */
1247
+ async function run(flags, extraOptions) {
1248
+ consola.consola.start("Initializing app...");
1249
+ const { execCommand, packageManager } = await ensurePackageJson();
1250
+ runInstall(packageManager, REQUIRED_DEPENDENCIES);
1251
+ const { config, domains } = await ensureCommerceAppConfig(process.cwd(), extraOptions?.formatConfig ?? true, flags);
1252
+ installDependencies(packageManager, domains);
1253
+ const pkg = await _npmcli_package_json.default.load(process.cwd());
1254
+ pkg.update({
1255
+ name: config.metadata.id,
1256
+ version: config.metadata.version,
1257
+ description: config.metadata.description
1258
+ });
1259
+ await pkg.save();
1260
+ await runGeneration(config, execCommand);
1261
+ await ensureAppConfig(domains);
1262
+ await ensureInstallYaml(domains);
1263
+ await writePostinstallHook(execCommand);
1264
+ consola.consola.success("Initialization complete!");
1265
+ consola.consola.box([
1266
+ "Next steps:",
1267
+ " - Review and customize app.commerce.config.*",
1268
+ " - Build and deploy your app"
1269
+ ].join("\n"));
1270
+ }
1271
+ /** Run the init command */
993
1272
  async function exec() {
994
1273
  try {
995
- consola.consola.start("Initializing app...");
996
- const { execCommand, packageManager } = await ensurePackageJson();
997
- runInstall(packageManager, REQUIRED_DEPENDENCIES);
998
- const { config, domains } = await ensureCommerceAppConfig();
999
- installDependencies(packageManager, domains);
1000
- (0, child_process.execSync)(`npm pkg set name="${config.metadata.id}"`);
1001
- (0, child_process.execSync)(`npm pkg set version="${config.metadata.version}"`);
1002
- (0, child_process.execSync)(`npm pkg set description="${config.metadata.description}"`);
1003
- await runGeneration(config, execCommand);
1004
- await ensureAppConfig(domains);
1005
- await ensureInstallYaml(domains);
1006
- consola.consola.success("Initialization complete!");
1007
- consola.consola.box([
1008
- "Next steps:",
1009
- " - Review and customize app.commerce.config.*",
1010
- " - Build and deploy your app"
1011
- ].join("\n"));
1274
+ await run();
1012
1275
  } catch (error) {
1013
1276
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
1014
- consola.consola.error(error);
1277
+ else consola.consola.error(error);
1015
1278
  process.exit(1);
1016
1279
  }
1017
1280
  }