@adobe/aio-commerce-lib-app 1.3.1 → 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 +37 -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 -321
  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 -316
  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
+ }
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
+ }
42
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,177 +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
- "webhooks",
351
- "adminUiSdk"
352
- ].some((feature) => features.has(feature))) {
353
- extConfig.operations.workerProcess.push({
354
- type: "action",
355
- impl: `${PACKAGE_NAME}/installation`
356
- });
357
- extConfig.runtimeManifest.packages[PACKAGE_NAME].actions.installation = createActionDefinition("installation", { requiresEncryptionKey: hasPasswordFieldsInSchema }, {
358
- inputs: {
359
- ...COMMERCE_ACTION_INPUTS,
360
- LOG_LEVEL: "$LOG_LEVEL"
361
- },
362
- limits: { timeout: 6e5 }
363
- });
364
- }
365
- return extConfig;
366
- }
367
- /** Builds the ext.config.yaml configuration for the business configuration extension. */
368
- function buildBusinessConfigurationExtConfig() {
369
- const actions = [{
370
- name: "config",
371
- templateFile: "config.js.template",
372
- requiresEncryptionKey: true
373
- }, {
374
- name: "scope-tree",
375
- templateFile: "scope-tree.js.template"
376
- }];
377
- return {
378
- hooks: { "pre-app-build": "EXTENSION=configuration/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
379
- operations: { workerProcess: actions.map((action) => ({
380
- type: "action",
381
- impl: `${PACKAGE_NAME}/${action.name}`
382
- })) },
383
- runtimeManifest: { packages: { [PACKAGE_NAME]: {
384
- license: "Apache-2.0",
385
- actions: Object.fromEntries(actions.map((action) => [action.name, createActionDefinition(action.name, action)]))
386
- } } }
387
- };
388
- }
389
-
390
- //#endregion
391
- //#region source/commands/generate/actions/main.ts
392
- const __dirname$1 = (0, path.dirname)((0, url.fileURLToPath)(require("url").pathToFileURL(__filename).href));
393
- async function run$4(appManifest) {
394
- await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, EXTENSIBILITY_EXTENSION_POINT_ID), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID);
395
- if (require_validate.hasBusinessConfigSchema(appManifest)) await generateActionFiles(appManifest, getRuntimeActions(await updateExtConfig(appManifest, CONFIGURATION_EXTENSION_POINT_ID), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID);
396
- }
397
- /** Run the generate actions command */
398
- async function exec$5() {
515
+ async function readExtConfig(extensionPointId) {
516
+ const extConfigPath = (0, path.join)(await require_config.getProjectRootDirectory(), getExtConfigPath(extensionPointId));
399
517
  try {
400
- await run$4(await loadAppManifest());
518
+ return {
519
+ path: extConfigPath,
520
+ doc: await readYamlFile(extConfigPath)
521
+ };
401
522
  } catch (error) {
402
- if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
403
- consola.consola.error(error);
404
- 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"));
405
529
  }
406
530
  }
407
531
  /** Update the ext.config.yaml file */
408
532
  async function updateExtConfig(appConfig, extensionPointId) {
409
533
  consola.consola.info(`Updating ext.config.yaml for ${extensionPointId}...`);
410
- const extConfigPath = (0, path.join)(await require_config.makeOutputDirFor(getExtensionPointFolderPath(extensionPointId)), "ext.config.yaml");
411
- const extConfigDoc = await readYamlFile(extConfigPath);
534
+ await require_config.makeOutputDirFor(getExtensionPointFolderPath(extensionPointId));
535
+ const { path: extConfigPath, doc: extConfigDoc } = await readExtConfig(extensionPointId);
412
536
  let extConfig;
413
537
  switch (extensionPointId) {
414
538
  case EXTENSIBILITY_EXTENSION_POINT_ID:
@@ -417,24 +541,26 @@ async function updateExtConfig(appConfig, extensionPointId) {
417
541
  case CONFIGURATION_EXTENSION_POINT_ID:
418
542
  extConfig = buildBusinessConfigurationExtConfig();
419
543
  break;
420
- default: throw new Error(`Unsupported extension point ID: ${extensionPointId}`);
544
+ case BACKEND_UI_EXTENSION_POINT_ID:
545
+ extConfig = buildAdminUiSdkExtConfig();
546
+ break;
421
547
  }
422
548
  await createOrUpdateExtConfig(extConfigPath, extConfig, extConfigDoc);
423
549
  return extConfig;
424
550
  }
425
551
  /** Generate the action files */
426
- async function generateActionFiles(appManifest, actions, extensionPointId) {
552
+ async function generateActionFiles(appManifest, actions, extensionPointId, templatesDir) {
427
553
  consola.consola.start("Generating runtime actions...");
428
- 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();
429
556
  const outputFiles = [];
430
- const templatesDir = (0, path.join)(__dirname$1, "generate/actions/templates");
431
557
  for (const action of actions) {
432
558
  let template = await (0, fs_promises.readFile)((0, path.join)(templatesDir, action.templateFile), "utf-8");
433
559
  if (action.name === "installation") {
434
560
  const scriptsTemplate = await generateCustomScriptsTemplate(await (0, fs_promises.readFile)((0, path.join)(templatesDir, "app-management", "custom-scripts.js.template"), "utf-8"), appManifest);
435
561
  template = applyCustomScripts(template, scriptsTemplate);
436
562
  }
437
- const actionPath = (0, path.join)(outputDir, `${action.name}.js`);
563
+ const actionPath = (0, path.join)(projectRoot, getActionPath(extensionPointId, action.name));
438
564
  await (0, fs_promises.writeFile)(actionPath, template, "utf-8");
439
565
  outputFiles.push(` ${(0, path.relative)(process.cwd(), actionPath)}`);
440
566
  }
@@ -457,7 +583,7 @@ function applyCustomScripts(installationTemplate, customScriptsTemplate) {
457
583
  async function generateCustomScriptsTemplate(template, appManifest) {
458
584
  if (!require_webhooks.hasCustomInstallationSteps(appManifest)) return null;
459
585
  const projectRoot = await require_config.getProjectRootDirectory();
460
- 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));
461
587
  const customSteps = appManifest.installation.customInstallationSteps;
462
588
  const importStatements = customSteps.map((step, index) => {
463
589
  let relativeImportPath = (0, path.relative)(installationActionDir, (0, path.join)(projectRoot, step.script));
@@ -471,36 +597,92 @@ async function generateCustomScriptsTemplate(template, appManifest) {
471
597
  }).join("\n");
472
598
  return template.replace(CUSTOM_IMPORTS_PLACEHOLDER, importStatements).replace(CUSTOM_SCRIPTS_MAP_PLACEHOLDER, scriptMap);
473
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
+ }
474
643
 
475
644
  //#endregion
476
645
  //#region source/commands/generate/manifest/main.ts
477
- async function run$3(appConfig) {
646
+ async function run$4(appConfig) {
478
647
  consola.consola.info("Generating app manifest...");
479
648
  const contents = (0, safe_stable_stringify.stringify)(appConfig, null, 2);
480
- 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");
481
650
  consola.consola.success(`Generated ${APP_MANIFEST_FILE}`);
482
651
  }
483
652
  /** Run the generate manifest command */
484
653
  async function exec$4() {
485
654
  try {
486
- await run$3(await loadAppManifest());
655
+ await run$4(await loadAppManifest());
487
656
  } catch (error) {
488
657
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
489
- consola.consola.error(error);
658
+ else consola.consola.error(error);
490
659
  process.exit(1);
491
660
  }
492
661
  }
493
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
+
494
675
  //#endregion
495
676
  //#region source/commands/generate/schema/main.ts
496
- async function run$2(appConfig) {
497
- consola.consola.info("Generating configuration schema...");
677
+ async function run$3(appConfig) {
498
678
  if (!require_validate.hasBusinessConfigSchema(appConfig)) {
499
- consola.consola.warn("Business configuration schema not found");
679
+ consola.consola.debug("Business configuration schema not found in application configuration. Nothing to do.");
500
680
  return;
501
681
  }
502
- 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();
503
684
  const envPath = (0, path.join)(projectDir, ".env");
685
+ await touch(envPath);
504
686
  process.loadEnvFile(envPath);
505
687
  if (appConfig.businessConfig.schema.some((field) => field.type === "password")) {
506
688
  const packageExec = require_config.getExecCommand(await require_config.detectPackageManager(projectDir));
@@ -508,35 +690,35 @@ async function run$2(appConfig) {
508
690
  else (0, child_process.execSync)(`${packageExec} aio-commerce-lib-config encryption setup`);
509
691
  }
510
692
  const contents = (0, safe_stable_stringify.stringify)(appConfig.businessConfig.schema, null, 2);
511
- 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");
512
694
  consola.consola.success(`Generated ${CONFIG_SCHEMA_FILE_NAME}`);
513
695
  }
514
- /** Run the generate manifest command */
696
+ /** Run the generate schema command */
515
697
  async function exec$3() {
516
698
  try {
517
- await run$2(await loadAppManifest());
699
+ await run$3(await loadAppManifest());
518
700
  } catch (error) {
519
701
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
520
- consola.consola.error(error);
702
+ else consola.consola.error(error);
521
703
  process.exit(1);
522
704
  }
523
705
  }
524
706
 
525
707
  //#endregion
526
708
  //#region source/commands/hooks/postinstall.ts
527
- async function run$1(appManifest) {
709
+ async function run$2(appManifest, templatesDir) {
710
+ await run$5(appManifest, templatesDir);
528
711
  await run$4(appManifest);
529
712
  await run$3(appManifest);
530
- await run$2(appManifest);
531
713
  }
532
714
  /** Runs the postinstall hook */
533
715
  async function exec$2() {
534
716
  consola.default.debug("Running lib-app postinstall hook");
535
717
  try {
536
- await run$1(await loadAppManifest());
718
+ await run$2(await loadAppManifest());
537
719
  } catch (error) {
538
720
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.default.error(error.display());
539
- consola.default.error(error);
721
+ else consola.default.error(error);
540
722
  process.exit(1);
541
723
  }
542
724
  }
@@ -592,19 +774,27 @@ function resolveImsS2SContext() {
592
774
  }
593
775
  /**
594
776
  * Syncs the IMS credentials environment variables from the configured IMS context in
595
- * 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`.
596
778
  */
597
779
  async function syncImsCredentials() {
598
780
  const envPath = resolveEnvPath();
781
+ if (!(0, fs.existsSync)(envPath)) return {
782
+ ok: false,
783
+ reason: "missing-env"
784
+ };
599
785
  const envVars = dotenv.default.parse((0, fs.readFileSync)(envPath, "utf8"));
600
786
  const imsContext = await resolveImsS2SContext();
601
- if (!imsContext) return;
787
+ if (!imsContext) return {
788
+ ok: false,
789
+ reason: "no-ims-context"
790
+ };
602
791
  const { data } = imsContext;
603
792
  for (const [key, value] of Object.entries(data)) {
604
793
  const oauthKey = IMS_KEYS[key];
605
794
  if (!oauthKey) continue;
606
795
  if (!envVars[oauthKey] || envVars[oauthKey] !== value) replaceEnvVar(envPath, oauthKey, value);
607
796
  }
797
+ return { ok: true };
608
798
  }
609
799
 
610
800
  //#endregion
@@ -612,17 +802,26 @@ async function syncImsCredentials() {
612
802
  /**
613
803
  * Runs the pre-app-build hook for the given extension.
614
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.
615
806
  */
616
- async function run(extension) {
807
+ async function run$1(extension, templatesDir = TEMPLATES_DIR) {
617
808
  const appManifest = await loadAppManifest();
618
809
  if (extension === "extensibility/1") {
619
- 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);
620
813
  consola.default.info("Syncing IMS credentials...");
621
814
  await syncImsCredentials();
622
815
  return;
623
816
  }
624
817
  if (extension === "configuration/1") {
625
- 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);
626
825
  return;
627
826
  }
628
827
  throw new Error(`Unsupported extension: ${extension}`);
@@ -633,10 +832,10 @@ async function exec$1() {
633
832
  const extension = process.env.EXTENSION;
634
833
  try {
635
834
  if (!extension) throw new Error("EXTENSION environment variable is not set");
636
- await run(extension);
835
+ await run$1(extension);
637
836
  } catch (error) {
638
837
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.default.error(error.display());
639
- consola.default.error(error);
838
+ else consola.default.error(error);
640
839
  process.exit(1);
641
840
  }
642
841
  }
@@ -671,7 +870,7 @@ const DOMAIN_DEFAULTS = {
671
870
  },
672
871
  events: [{
673
872
  name: "plugin.sample_event",
674
- fields: [],
873
+ fields: [{ name: "*" }],
675
874
  label: "Sample Event",
676
875
  description: "Use case description for the event.",
677
876
  runtimeActions: ["my-package/handle-sample-event"]
@@ -693,12 +892,50 @@ const DOMAIN_DEFAULTS = {
693
892
  name: "sample-step",
694
893
  description: "Use case description for the step.",
695
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
+ }
696
920
  }]
697
921
  };
698
922
 
699
923
  //#endregion
700
924
  //#region source/commands/init/utils.ts
701
- /** 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. */
702
939
  async function promptForCommerceAppConfig() {
703
940
  const configFormat = await consola.default.prompt("What format do you want to use for the config file?", {
704
941
  type: "select",
@@ -734,6 +971,10 @@ async function promptForCommerceAppConfig() {
734
971
  label: "Commerce Events",
735
972
  value: "eventing.commerce"
736
973
  },
974
+ {
975
+ label: "Commerce Webhooks",
976
+ value: "webhooks"
977
+ },
737
978
  {
738
979
  label: "External Events",
739
980
  value: "eventing.external"
@@ -744,10 +985,13 @@ async function promptForCommerceAppConfig() {
744
985
  }
745
986
  ]
746
987
  });
988
+ const configFile = `${COMMERCE_APP_CONFIG_FILE}.${configFormat}`;
989
+ const domains = new Set(featuresToAdd);
990
+ consola.default.log("");
747
991
  return {
748
992
  appName,
749
- configFile: `${COMMERCE_APP_CONFIG_FILE}.${configFormat}`,
750
- domains: new Set(featuresToAdd),
993
+ configFile,
994
+ domains,
751
995
  configFormat
752
996
  };
753
997
  }
@@ -772,6 +1016,7 @@ async function getDefaultCommerceAppConfig(cwd, { appName, configFormat, domains
772
1016
  external: DOMAIN_DEFAULTS["eventing.external"]
773
1017
  };
774
1018
  if (domains.has("installation.customInstallationSteps")) defaultConfig.installation = { customInstallationSteps: DOMAIN_DEFAULTS["installation.customInstallationSteps"] };
1019
+ if (domains.has("webhooks")) defaultConfig.webhooks = DOMAIN_DEFAULTS.webhooks;
775
1020
  return [
776
1021
  importStatement,
777
1022
  "",
@@ -786,9 +1031,8 @@ async function getDefaultCommerceAppConfig(cwd, { appName, configFormat, domains
786
1031
  */
787
1032
  async function addExtensionPointToAppConfig(extensionPointId, rootDirectory, commentBefore) {
788
1033
  const appConfigPath = (0, path.join)(rootDirectory, APP_CONFIG_FILE);
789
- const extensionPointFolderPath = (0, path.join)(rootDirectory, getExtensionPointFolderPath(extensionPointId));
790
1034
  let doc;
791
- 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)));
792
1036
  try {
793
1037
  doc = await readYamlFile(appConfigPath);
794
1038
  } catch (error) {
@@ -846,12 +1090,12 @@ async function addExtensionPointToInstallYaml(extensionPointId, rootDirectory, c
846
1090
 
847
1091
  //#endregion
848
1092
  //#region source/commands/init/lib.ts
849
- /** Ensure app.commerce.config file exists, allow creating if it doesn't */
850
- 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) {
851
1095
  let config = null;
852
1096
  try {
853
1097
  config = await require_config.readCommerceAppConfig(cwd);
854
- } catch (_) {}
1098
+ } catch {}
855
1099
  if (config) try {
856
1100
  const validatedConfig = require_validate.validateCommerceAppConfig(config);
857
1101
  consola.consola.success(`${COMMERCE_APP_CONFIG_FILE} found and is valid. Continuing...`);
@@ -862,119 +1106,126 @@ async function ensureCommerceAppConfig(cwd = process.cwd()) {
862
1106
  } catch (error) {
863
1107
  throw new Error(`${COMMERCE_APP_CONFIG_FILE} is invalid`, { cause: error });
864
1108
  }
865
- consola.consola.warn(`${COMMERCE_APP_CONFIG_FILE} not found.`);
866
- if (!await consola.consola.prompt(`Do you want to create a ${"app.commerce.config"} file? (y/n)`, {
867
- type: "confirm",
868
- initial: true,
869
- default: false
870
- })) throw new Error("Initialization cancelled.");
871
- 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();
872
1118
  try {
873
1119
  const configContent = await getDefaultCommerceAppConfig(cwd, answers);
874
- consola.consola.info(`Creating ${answers.configFile}...`);
875
1120
  const path$2 = (0, path.join)(await require_config.getProjectRootDirectory(cwd), answers.configFile);
876
- await (0, fs_promises.writeFile)(path$2, await prettier.format(configContent, {
877
- semi: true,
878
- quoteStyle: "double",
879
- arrowParens: "always",
880
- bracketSameLine: true,
881
- bracketSpacing: true,
882
- trailingComma: "all",
883
- tabWidth: 2,
884
- useTabs: false,
885
- printWidth: 80,
886
- filepath: path$2
887
- }), "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);
888
1125
  consola.consola.success(`Created ${answers.configFile}`);
889
1126
  return {
890
- config: await require_config.parseCommerceAppConfig(cwd),
1127
+ config,
891
1128
  domains: answers.domains
892
1129
  };
893
1130
  } catch (error) {
894
1131
  throw new Error(`Failed to create ${answers.configFile}`, { cause: error });
895
1132
  }
896
1133
  }
897
- /** 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
+ */
898
1138
  async function ensurePackageJson(cwd = process.cwd()) {
899
- const packageJson = await require_config.readPackageJson(cwd);
900
- if (!packageJson) {
1139
+ const existing = await require_config.readPackageJson(cwd);
1140
+ let packageJson;
1141
+ if (existing) packageJson = existing;
1142
+ else {
901
1143
  consola.consola.warn("package.json not found. Creating one...");
902
- const packageJsonContent = {
1144
+ packageJson = {
903
1145
  name: "my-commerce-app",
904
1146
  version: "1.0.0",
905
- private: true,
906
- scripts: { postinstall: "npx aio-commerce-lib-app hooks postinstall" }
1147
+ private: true
907
1148
  };
908
- 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");
909
1150
  consola.consola.success("Wrote package.json");
910
- return {
911
- packageJson: packageJsonContent,
912
- packageManager: "npm",
913
- execCommand: "npx"
914
- };
915
1151
  }
916
- const packageManager = await require_config.detectPackageManager();
1152
+ const packageManager = await require_config.detectPackageManager(cwd);
917
1153
  const execCommand = require_config.getExecCommand(packageManager);
918
- const postinstallScript = `${execCommand} aio-commerce-lib-app hooks postinstall`;
919
- packageJson.scripts ??= {};
920
- if (packageJson.scripts.postinstall === postinstallScript || packageJson.scripts.postinstall?.includes(postinstallScript)) {
921
- consola.consola.success(`postinstall script already configured in ${PACKAGE_JSON_FILE}`);
922
- return {
923
- packageJson,
924
- packageManager,
925
- execCommand
926
- };
927
- }
928
- if (packageJson.scripts.postinstall) {
929
- consola.consola.warn(`${PACKAGE_JSON_FILE} already has a postinstall script. Adding a new one...`);
930
- (0, child_process.execSync)(`npm pkg set scripts.postinstall="${`${packageJson.scripts.postinstall} && ${postinstallScript}`}"`);
931
- } else {
932
- consola.consola.info(`Adding postinstall script to ${PACKAGE_JSON_FILE}...`);
933
- (0, child_process.execSync)(`npm pkg set scripts.postinstall="${postinstallScript}"`);
934
- }
935
- consola.consola.success(`Added postinstall script to ${PACKAGE_JSON_FILE}`);
936
1154
  return {
937
1155
  packageJson,
938
1156
  packageManager,
939
1157
  execCommand
940
1158
  };
941
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
+ }
942
1183
  /** Ensure app.config.yaml has the extension reference */
943
1184
  async function ensureAppConfig(domains, cwd = process.cwd()) {
944
1185
  const rootDirectory = await require_config.getProjectRootDirectory(cwd);
945
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.");
946
1188
  await addExtensionPointToAppConfig(EXTENSIBILITY_EXTENSION_POINT_ID, rootDirectory, " This extension is required for app management. Do not remove.");
947
1189
  }
948
- /** 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
+ */
949
1196
  function runInstall(packageManager, dependencies, cwd = process.cwd()) {
1197
+ if (dependencies.length === 0) return;
950
1198
  const dependencyListString = dependencies.map((dependency) => ` - ${dependency}`).join("\n");
951
- consola.consola.info(`Installing the following dependencies with ${packageManager}:\n${dependencyListString}`);
952
- const packagesToInstall = dependencies.join(" ");
953
- const installCommand = {
954
- pnpm: `pnpm add ${packagesToInstall}`,
955
- yarn: `yarn add ${packagesToInstall}`,
956
- bun: `bun add ${packagesToInstall}`,
957
- npm: `npm install ${packagesToInstall}`
958
- }[packageManager];
959
- try {
960
- (0, child_process.execSync)(installCommand, {
961
- cwd,
962
- stdio: "inherit"
963
- });
964
- consola.consola.success("Dependencies installed successfully");
965
- } catch (error) {
966
- throw new Error(`Failed to install dependencies automatically. Please install manually: ${installCommand}`, { cause: error });
967
- }
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");
968
1209
  }
969
- /** 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
+ */
970
1216
  function installDependencies(packageManager, domains, cwd = process.cwd()) {
971
1217
  const packages = [];
972
- 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
+ }
973
1223
  runInstall(packageManager, packages, cwd);
974
1224
  }
975
1225
  /** Run the generation command */
976
1226
  async function runGeneration(appConfig, execCommand) {
977
1227
  try {
1228
+ await run$5(appConfig);
978
1229
  await run$4(appConfig);
979
1230
  await run$3(appConfig);
980
1231
  } catch (error) {
@@ -985,35 +1236,45 @@ async function runGeneration(appConfig, execCommand) {
985
1236
  async function ensureInstallYaml(domains, cwd = process.cwd()) {
986
1237
  const rootDirectory = await require_config.getProjectRootDirectory(cwd);
987
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.");
988
1240
  await addExtensionPointToInstallYaml(EXTENSIBILITY_EXTENSION_POINT_ID, rootDirectory, " This extension is required for app management. Do not remove.");
989
1241
  }
990
1242
 
991
1243
  //#endregion
992
1244
  //#region source/commands/init/main.ts
993
- const REQUIRED_DEPENDENCIES = ["@adobe/aio-commerce-lib-app", "@adobe/aio-commerce-sdk"];
994
- /** 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 */
995
1272
  async function exec() {
996
1273
  try {
997
- consola.consola.start("Initializing app...");
998
- const { execCommand, packageManager } = await ensurePackageJson();
999
- runInstall(packageManager, REQUIRED_DEPENDENCIES);
1000
- const { config, domains } = await ensureCommerceAppConfig();
1001
- installDependencies(packageManager, domains);
1002
- (0, child_process.execSync)(`npm pkg set name="${config.metadata.id}"`);
1003
- (0, child_process.execSync)(`npm pkg set version="${config.metadata.version}"`);
1004
- (0, child_process.execSync)(`npm pkg set description="${config.metadata.description}"`);
1005
- await runGeneration(config, execCommand);
1006
- await ensureAppConfig(domains);
1007
- await ensureInstallYaml(domains);
1008
- consola.consola.success("Initialization complete!");
1009
- consola.consola.box([
1010
- "Next steps:",
1011
- " - Review and customize app.commerce.config.*",
1012
- " - Build and deploy your app"
1013
- ].join("\n"));
1274
+ await run();
1014
1275
  } catch (error) {
1015
1276
  if (error instanceof _adobe_aio_commerce_lib_core_error.CommerceSdkValidationError) consola.consola.error(error.display());
1016
- consola.consola.error(error);
1277
+ else consola.consola.error(error);
1017
1278
  process.exit(1);
1018
1279
  }
1019
1280
  }