@adobe/aio-commerce-lib-app 2.0.0 → 2.1.0-alpha-20260923164312

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 (69) hide show
  1. package/CHANGELOG.md +10 -0
  2. package/README.md +4 -0
  3. package/dist/cjs/actions/app-config/index.cjs +3 -3
  4. package/dist/cjs/actions/association/index.cjs +1 -1
  5. package/dist/cjs/actions/config/index.cjs +1 -1
  6. package/dist/cjs/actions/installation/index.cjs +4 -4
  7. package/dist/cjs/actions/scope-tree/index.cjs +4 -3
  8. package/dist/cjs/cli/index.cjs +447 -0
  9. package/dist/cjs/cli/index.d.cts +40 -0
  10. package/dist/cjs/commands/index.cjs +73 -1717
  11. package/dist/cjs/{common-VSsQEHDv.cjs → common-B3KrOG-h.cjs} +1 -1
  12. package/dist/cjs/config/index.node.cjs +3 -3
  13. package/dist/{es/logging-CXesop3u.mjs → cjs/constants-tQUzmimN.cjs} +6 -18
  14. package/dist/cjs/{index.node-vcOo1rt0.cjs → index.node-De5ENMF8.cjs} +2 -2
  15. package/dist/cjs/{logging-Br3xfBNe.cjs → logging-QTsrSwPc.cjs} +0 -16
  16. package/dist/cjs/management/index.cjs +1 -1
  17. package/dist/cjs/{management-Bki3hlxL.cjs → management-DplOGozL.cjs} +11 -10
  18. package/dist/cjs/{validate-CwWYtRsS.cjs → validate-DkmBVWFX.cjs} +1 -1
  19. package/dist/cjs/web-src-DvGBiJI5.cjs +1521 -0
  20. package/dist/cjs/{webhooks-Bcq2Qy7q.cjs → webhooks-zXjrg32x.cjs} +57 -57
  21. package/dist/es/actions/app-config/index.mjs +3 -3
  22. package/dist/es/actions/association/index.mjs +1 -1
  23. package/dist/es/actions/config/index.mjs +1 -1
  24. package/dist/es/actions/installation/index.mjs +4 -4
  25. package/dist/es/actions/scope-tree/index.mjs +3 -2
  26. package/dist/es/cli/index.d.mts +40 -0
  27. package/dist/es/cli/index.mjs +435 -0
  28. package/dist/es/commands/index.mjs +29 -1666
  29. package/dist/es/{common-dy6NrZt8.mjs → common-BWUDGasu.mjs} +1 -1
  30. package/dist/es/config/index.node.mjs +3 -3
  31. package/dist/es/constants-CSQsZaDP.mjs +25 -0
  32. package/dist/es/{index.node-CW46zuxW.mjs → index.node-C5D5IzSs.mjs} +2 -2
  33. package/dist/es/logging-DbJHXSKh.mjs +32 -0
  34. package/dist/es/management/index.mjs +1 -1
  35. package/dist/es/{management-DgwAhaaJ.mjs → management-YDkb15SJ.mjs} +6 -5
  36. package/dist/es/{validate-NzW1FQwL.mjs → validate-Pd7RIA_3.mjs} +1 -1
  37. package/dist/es/web-src-CLEUvMl7.mjs +1345 -0
  38. package/dist/es/{webhooks-CHZ1SXxw.mjs → webhooks-BMBfTqwi.mjs} +58 -58
  39. package/package.json +11 -1
  40. /package/dist/cjs/{commands/templates → templates}/admin-ui/tsconfig.json +0 -0
  41. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/.babelrc +0 -0
  42. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/index.css +0 -0
  43. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/index.html +0 -0
  44. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/src/app.jsx +0 -0
  45. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/src/components/welcome.jsx +0 -0
  46. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src/src/pages/main-page.jsx +0 -0
  47. /package/dist/cjs/{commands/templates → templates}/admin-ui/web-src-template.d.ts +0 -0
  48. /package/dist/cjs/{commands/templates → templates}/app-management/app-config.js.template +0 -0
  49. /package/dist/cjs/{commands/templates → templates}/app-management/association.js.template +0 -0
  50. /package/dist/cjs/{commands/templates → templates}/app-management/custom-scripts.js.template +0 -0
  51. /package/dist/cjs/{commands/templates → templates}/app-management/installation.js.template +0 -0
  52. /package/dist/cjs/{commands/templates → templates}/business-configuration/config.js.template +0 -0
  53. /package/dist/cjs/{commands/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  54. /package/dist/cjs/{commands/templates → templates}/typescript/webpack-config.cjs +0 -0
  55. /package/dist/es/{commands/templates → templates}/admin-ui/tsconfig.json +0 -0
  56. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/.babelrc +0 -0
  57. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/index.css +0 -0
  58. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/index.html +0 -0
  59. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/src/app.jsx +0 -0
  60. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/src/components/welcome.jsx +0 -0
  61. /package/dist/es/{commands/templates → templates}/admin-ui/web-src/src/pages/main-page.jsx +0 -0
  62. /package/dist/es/{commands/templates → templates}/admin-ui/web-src-template.d.ts +0 -0
  63. /package/dist/es/{commands/templates → templates}/app-management/app-config.js.template +0 -0
  64. /package/dist/es/{commands/templates → templates}/app-management/association.js.template +0 -0
  65. /package/dist/es/{commands/templates → templates}/app-management/custom-scripts.js.template +0 -0
  66. /package/dist/es/{commands/templates → templates}/app-management/installation.js.template +0 -0
  67. /package/dist/es/{commands/templates → templates}/business-configuration/config.js.template +0 -0
  68. /package/dist/es/{commands/templates → templates}/business-configuration/scope-tree.js.template +0 -0
  69. /package/dist/es/{commands/templates → templates}/typescript/webpack-config.cjs +0 -0
@@ -0,0 +1,435 @@
1
+ /**
2
+ * @license
3
+ *
4
+ * Copyright 2026 Adobe. All rights reserved.
5
+ * This file is licensed to you under the Apache License, Version 2.0 (the "License");
6
+ * you may not use this file except in compliance with the License. You may obtain a copy
7
+ * of the License at http://www.apache.org/licenses/LICENSE-2.0
8
+ *
9
+ * Unless required by applicable law or agreed to in writing, software distributed under
10
+ * the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR REPRESENTATIONS
11
+ * OF ANY KIND, either express or implied. See the License for the specific language
12
+ * governing permissions and limitations under the License.
13
+ */
14
+
15
+ import { p as getProjectRootDirectory, r as parseCommerceAppConfig } from "../index.node-C5D5IzSs.mjs";
16
+ import { C as BACKEND_UI_V2_EXTENSION_POINT_ID, E as EXTENSIBILITY_EXTENSION_POINT_ID, T as CONFIGURATION_EXTENSION_POINT_ID, c as run$4, d as readExtConfig, f as updateExtConfig, g as getRuntimeActions, k as getExtensionPointFolderPath, l as generateActionFiles, n as prepareWebSourceImportAlias, o as run$5, t as generateWebSrc, u as prepareRuntimeAppConfigModule, v as loadAppManifest, x as TEMPLATES_DIR } from "../web-src-CLEUvMl7.mjs";
17
+ import { n as INSTALLATION_INVOCATION_SOURCE_HEADER, r as POST_APP_DEPLOY_INVOCATION_SOURCE } from "../common-BWUDGasu.mjs";
18
+ import { l as hasBackendUiV2Components } from "../webhooks-BMBfTqwi.mjs";
19
+ import { CommerceSdkValidationError } from "@adobe/aio-commerce-lib-core/error";
20
+ import { existsSync, readFileSync, writeFileSync } from "fs";
21
+ import path, { join } from "path";
22
+ import consola$1, { consola } from "consola";
23
+ import aioIms from "@adobe/aio-lib-ims";
24
+ import config from "@adobe/aio-lib-core-config";
25
+ import { colors } from "consola/utils";
26
+ import ky, { HTTPError } from "ky";
27
+ import * as v from "valibot";
28
+ import { setTimeout } from "timers/promises";
29
+ import dotenv from "dotenv";
30
+
31
+ //#region ../../packages-private/scripting-utils/source/aio/auth.ts
32
+ const { context: context$1, getToken } = aioIms;
33
+ /** Gets an IMS access token for the current CLI IMS context. */
34
+ async function getUserToken() {
35
+ const contextName = await context$1.getCurrent() ?? "cli";
36
+ return getToken(contextName, {});
37
+ }
38
+
39
+ //#endregion
40
+ //#region ../../packages-private/scripting-utils/source/aio/context.ts
41
+ /**
42
+ * Reads the current App Builder project and Runtime namespace from the local AIO CLI config.
43
+ * @throws If either the project or the runtime namespace is not configured.
44
+ */
45
+ function getAioProjectContext() {
46
+ const project = config.get("project");
47
+ const namespace = config.get("runtime.namespace");
48
+ if (!(project && namespace)) throw new Error("The current App Builder project and Runtime namespace are required");
49
+ return {
50
+ namespace,
51
+ project
52
+ };
53
+ }
54
+ /** Reads the configured AIO CLI environment, defaulting to `"prod"`. */
55
+ function getAioCliEnv() {
56
+ return config.get("cli.env") === "stage" ? "stage" : "prod";
57
+ }
58
+
59
+ //#endregion
60
+ //#region source/commands/hooks/post-app-deploy/polling.ts
61
+ const POLL_INTERVAL_MS = 1e3;
62
+ const UpgradeStateSchema = v.object({
63
+ failure: v.optional(v.object({
64
+ key: v.string(),
65
+ message: v.optional(v.string())
66
+ })),
67
+ id: v.string(),
68
+ status: v.picklist([
69
+ "pending",
70
+ "in-progress",
71
+ "succeeded",
72
+ "failed"
73
+ ])
74
+ });
75
+ /** Reads the latest upgrade state from the installation action. */
76
+ async function getUpgradeState(request) {
77
+ try {
78
+ const response = await ky.get(request.endpoint, { headers: request.headers });
79
+ if (response.status === 204) return null;
80
+ const parsed = v.safeParse(UpgradeStateSchema, await response.json());
81
+ return parsed.success ? parsed.output : null;
82
+ } catch (error) {
83
+ consola.debug("Unable to read upgrade status; stopping upgrade polling.", { error });
84
+ return null;
85
+ }
86
+ }
87
+ /** Waits for an automatic upgrade when its status remains available. */
88
+ async function waitForAutomaticUpgrade(request) {
89
+ const state = await getUpgradeState(request);
90
+ if (!state) return;
91
+ if (state.status === "succeeded") {
92
+ consola.success("App upgrade completed.\n");
93
+ return;
94
+ }
95
+ if (state.status === "failed") {
96
+ const reason = state.failure?.message ?? state.failure?.key ?? "The lifecycle attempt failed";
97
+ throw new Error(`App upgrade failed: ${reason}`);
98
+ }
99
+ consola.start("App upgrade is in progress...");
100
+ await setTimeout(POLL_INTERVAL_MS);
101
+ return waitForAutomaticUpgrade(request);
102
+ }
103
+
104
+ //#endregion
105
+ //#region source/commands/hooks/post-app-deploy/main.ts
106
+ /** Returns true for a no-op reason defined by the upgrade API contract. */
107
+ function isSkippedReason(reason) {
108
+ return reason === "already-current" || reason === "not-associated" || reason === "not-installed";
109
+ }
110
+ /** Returns true if the result indicates that the upgrade was skipped. */
111
+ function isSkippedResult(result) {
112
+ return "skipped" in result;
113
+ }
114
+ /** Invokes the upgrade action. */
115
+ async function createUpgradeRequest() {
116
+ const { project, namespace } = getAioProjectContext();
117
+ const token = await getUserToken();
118
+ const endpoint = `https://${namespace}.adobeioruntime.net/api/v1/web/app-management/installation`;
119
+ const ioEventsEnv = getAioCliEnv();
120
+ const ioEventsUrl = ioEventsEnv === "stage" ? "https://events-stage.adobe.io" : "https://events.adobe.io";
121
+ return {
122
+ body: {
123
+ appData: {
124
+ consumerOrgId: project.org.id,
125
+ orgName: project.org.name,
126
+ projectId: project.id,
127
+ projectName: project.name,
128
+ projectTitle: project.title,
129
+ workspaceId: project.workspace.id,
130
+ workspaceName: project.workspace.name,
131
+ workspaceTitle: project.workspace.title
132
+ },
133
+ ioEventsEnv,
134
+ ioEventsUrl
135
+ },
136
+ endpoint,
137
+ headers: {
138
+ Authorization: `Bearer ${token}`,
139
+ [INSTALLATION_INVOCATION_SOURCE_HEADER]: POST_APP_DEPLOY_INVOCATION_SOURCE,
140
+ "x-gw-ims-org-id": project.org.ims_org_id
141
+ }
142
+ };
143
+ }
144
+ /** Invokes the upgrade action. */
145
+ async function invokeAction(request) {
146
+ consola.debug(`Upgrade endpoint: ${request.endpoint}`);
147
+ try {
148
+ return await ky.post(request.endpoint, {
149
+ headers: request.headers,
150
+ json: request.body
151
+ }).json();
152
+ } catch (error) {
153
+ if (error instanceof HTTPError) {
154
+ const details = await error.response.json();
155
+ if (error.response.status === 409 && isSkippedReason(details.reason)) return {
156
+ reason: details.reason,
157
+ skipped: true
158
+ };
159
+ throw new Error(`Failed to trigger app upgrade (HTTP ${error.response.status}): ${JSON.stringify(details, null, 2)}`, { cause: error });
160
+ }
161
+ throw error;
162
+ }
163
+ }
164
+ /** Invokes the deployed app's upgrade endpoint. */
165
+ async function run$3() {
166
+ const { upgradeMode } = (await parseCommerceAppConfig()).metadata;
167
+ consola.log("");
168
+ consola.start("Checking for app upgrades...");
169
+ const request = await createUpgradeRequest();
170
+ const result = await invokeAction(request);
171
+ if (isSkippedResult(result)) {
172
+ consola.info(`No upgrade was run: ${result.reason}.\n`);
173
+ return result;
174
+ }
175
+ if (upgradeMode === "manual") consola.success(`You have set ${colors.cyan("metadata.upgradeMode")} to ${colors.cyan("manual")}. The upgrade plan has been created but will not be executed.`);
176
+ else consola.success(`You have set ${colors.cyan("metadata.upgradeMode")} to ${colors.cyan("auto")}. The upgrade plan has been created. Execution will begin shortly.`);
177
+ consola.box(["Upgrade plan", JSON.stringify(result.plan, null, 2)].join("\n\n"));
178
+ if (upgradeMode === "auto") await waitForAutomaticUpgrade(request);
179
+ return result;
180
+ }
181
+
182
+ //#endregion
183
+ //#region ../../packages-private/scripting-utils/source/env.ts
184
+ /**
185
+ * This module exports shared environment utilities for the AIO Commerce SDK.
186
+ * @packageDocumentation
187
+ */
188
+ const { context } = aioIms;
189
+ const IMS_KEYS = {
190
+ client_id: "AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
191
+ client_secrets: "AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
192
+ ims_org_id: "AIO_COMMERCE_AUTH_IMS_ORG_ID",
193
+ scopes: "AIO_COMMERCE_AUTH_IMS_SCOPES",
194
+ technical_account_email: "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
195
+ technical_account_id: "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID"
196
+ };
197
+ /**
198
+ * Replaces or creates an environment variable in a .env file and sets it in the
199
+ * current process.
200
+ * @param filePath - The path to the .env file
201
+ * @param key - The environment variable key to replace or create
202
+ * @param value - The new value for the environment variable
203
+ */
204
+ function replaceEnvVar(filePath, key, value) {
205
+ const envPath = path.resolve(filePath);
206
+ const envLines = readFileSync(envPath, "utf8").split("\n");
207
+ const formattedValue = value.includes(" ") ? `"${value}"` : value;
208
+ let keyExists = false;
209
+ const updatedLines = envLines.map((line) => {
210
+ if (line.trim().startsWith("#") || !line.includes("=")) return line;
211
+ const [currentKey] = line.split("=");
212
+ if (currentKey === key) {
213
+ keyExists = true;
214
+ return `${key}=${formattedValue}`;
215
+ }
216
+ return line;
217
+ });
218
+ if (!keyExists) updatedLines.push(`${key}=${formattedValue}`);
219
+ writeFileSync(envPath, updatedLines.join("\n"), "utf8");
220
+ process.env[key] = value;
221
+ }
222
+ /**
223
+ * Sets the `NODE_ENV` environment variable in the current process and in the app
224
+ * `.env` file, so the web bundler (Parcel) ships the matching React build.
225
+ * Creates the `.env` if absent.
226
+ * @param mode - The environment mode to set in `NODE_ENV`.
227
+ * @param projectRoot - Resolved project root containing the `.env` file.
228
+ */
229
+ function setNodeEnv(mode, projectRoot) {
230
+ const envPath = path.join(projectRoot, ".env");
231
+ if (!existsSync(envPath)) writeFileSync(envPath, "", "utf8");
232
+ replaceEnvVar(envPath, "NODE_ENV", mode);
233
+ }
234
+ /** Resolves the IMS server to server context from the project workspace credentials. */
235
+ function resolveImsS2SContext() {
236
+ const [credential] = (config.get("project.workspace.details.credentials") ?? []).filter(({ integration_type }) => integration_type === "oauth_server_to_server").map(({ name }) => name);
237
+ if (!credential) return Promise.resolve(null);
238
+ return context.get(credential);
239
+ }
240
+ /**
241
+ * Syncs the IMS credentials environment variables from the configured IMS context in
242
+ * the .env file, in a way that is compatible with `@adobe/aio-commerce-lib-auth`.
243
+ * @param projectRoot - Resolved project root containing the `.env` file.
244
+ */
245
+ async function syncImsCredentials(projectRoot) {
246
+ const envPath = path.join(projectRoot, ".env");
247
+ if (!existsSync(envPath)) return {
248
+ ok: false,
249
+ reason: "missing-env"
250
+ };
251
+ const envVars = dotenv.parse(readFileSync(envPath, "utf8"));
252
+ const imsContext = await resolveImsS2SContext();
253
+ if (!imsContext) return {
254
+ ok: false,
255
+ reason: "no-ims-context"
256
+ };
257
+ const { data } = imsContext;
258
+ for (const [key, value] of Object.entries(data)) {
259
+ const oauthKey = IMS_KEYS[key];
260
+ if (!oauthKey) continue;
261
+ if (!envVars[oauthKey] || envVars[oauthKey] !== value) replaceEnvVar(envPath, oauthKey, value);
262
+ }
263
+ return { ok: true };
264
+ }
265
+
266
+ //#endregion
267
+ //#region source/commands/hooks/pre-app-build.ts
268
+ /**
269
+ * Runs the pre-app-build hook for the given extension.
270
+ * @param extension - The extension to run the hook for.
271
+ * @param projectRoot - Resolved project root containing extension files.
272
+ * @param templatesDir - Directory containing action templates.
273
+ * @param options - Hook options.
274
+ * @param options.isDevSession - Whether the build runs within `aio app run` or `aio app dev`, which keeps `NODE_ENV` untouched.
275
+ */
276
+ async function run$2(extension, projectRoot, templatesDir = TEMPLATES_DIR, { isDevSession = false } = {}) {
277
+ const appManifest = await loadAppManifest(projectRoot);
278
+ await prepareRuntimeAppConfigModule(appManifest, projectRoot);
279
+ if (extension === "extensibility/1") {
280
+ const { doc: extensibilityExtConfig } = await readExtConfig(EXTENSIBILITY_EXTENSION_POINT_ID, projectRoot);
281
+ await run$4(appManifest, projectRoot);
282
+ await generateActionFiles(appManifest, getRuntimeActions(extensibilityExtConfig.toJS(), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID, templatesDir, projectRoot);
283
+ consola$1.info("Syncing IMS credentials...");
284
+ await syncImsCredentials(projectRoot);
285
+ return;
286
+ }
287
+ if (extension === "configuration/1") {
288
+ const { doc: businessConfigExtConfig } = await readExtConfig(CONFIGURATION_EXTENSION_POINT_ID, projectRoot);
289
+ await run$5(appManifest, projectRoot);
290
+ await generateActionFiles(appManifest, getRuntimeActions(businessConfigExtConfig.toJS(), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID, templatesDir, projectRoot);
291
+ return;
292
+ }
293
+ if (extension === "backend-ui/2") {
294
+ if (hasBackendUiV2Components(appManifest)) {
295
+ const extConfig = await updateExtConfig(appManifest, BACKEND_UI_V2_EXTENSION_POINT_ID, projectRoot);
296
+ if (extConfig.operations?.view) {
297
+ await prepareWebSourceImportAlias(extConfig, projectRoot);
298
+ await generateWebSrc(extConfig, appManifest.metadata.displayName, projectRoot, templatesDir);
299
+ if (!isDevSession) await setNodeEnv("production", projectRoot);
300
+ }
301
+ }
302
+ return;
303
+ }
304
+ throw new Error(`Unsupported extension: ${extension}`);
305
+ }
306
+
307
+ //#endregion
308
+ //#region source/commands/hooks/pre-app-dev.ts
309
+ /**
310
+ * Resets the web build back to development by setting `NODE_ENV` in the process and the project `.env`.
311
+ * @param projectRoot - Resolved project root containing the `.env` file.
312
+ */
313
+ async function run$1(projectRoot) {
314
+ await setNodeEnv("development", projectRoot);
315
+ }
316
+
317
+ //#endregion
318
+ //#region source/commands/hooks/pre-app-run.ts
319
+ /**
320
+ * Resets the web build back to development by setting `NODE_ENV` in the process and the project `.env`.
321
+ * @param projectRoot - Resolved project root containing the `.env` file.
322
+ */
323
+ async function run(projectRoot) {
324
+ await setNodeEnv("development", projectRoot);
325
+ }
326
+
327
+ //#endregion
328
+ //#region source/cli/index.ts
329
+ /**
330
+ * Internal App Builder hook handlers, loaded by the hook files that
331
+ * `@adobe/aio-commerce-lib-app` generates. Not part of the public API.
332
+ * @packageDocumentation
333
+ */
334
+ const USER_HOOKS_DIR = "hooks";
335
+ const USER_HOOK_FILE_EXTENSIONS = [
336
+ ".ts",
337
+ ".mts",
338
+ ".cts",
339
+ ".js",
340
+ ".mjs",
341
+ ".cjs"
342
+ ];
343
+ let isDevSession = false;
344
+ /**
345
+ * Finds the user hook file for a hook, looking in the extension point's `hooks`
346
+ * folder first and then in the project root `hooks` folder.
347
+ *
348
+ * @param projectRoot - Resolved project root.
349
+ * @param extensionPointId - The extension point ID, e.g. "commerce/backend-ui/2".
350
+ * @param hookName - The hook name, e.g. "pre-app-build".
351
+ */
352
+ function findUserHook(projectRoot, extensionPointId, hookName) {
353
+ const dirs = [join(projectRoot, getExtensionPointFolderPath(extensionPointId), USER_HOOKS_DIR), join(projectRoot, USER_HOOKS_DIR)];
354
+ for (const dir of dirs) {
355
+ const matches = USER_HOOK_FILE_EXTENSIONS.map((extension) => join(dir, `${hookName}${extension}`)).filter((file) => existsSync(file));
356
+ if (matches.length > 1) throw new Error(`Found more than one "${hookName}" hook file in ${dir}: ${matches.join(", ")}. Keep only one.`);
357
+ if (matches.length === 1) return matches[0];
358
+ }
359
+ return null;
360
+ }
361
+ /**
362
+ * Runs the user hook file for a hook, if there is one.
363
+ *
364
+ * @param projectRoot - Resolved project root.
365
+ * @param hookName - The hook name, e.g. "pre-app-build".
366
+ * @param extension - The extension the hook is registered for.
367
+ * @param hookArgs - The argument the aio CLI passed to the hook.
368
+ */
369
+ async function runUserHook(projectRoot, hookName, extension, hookArgs) {
370
+ const extensionPoint = `commerce/${extension}`;
371
+ const file = findUserHook(projectRoot, extensionPoint, hookName);
372
+ if (!file) return;
373
+ const { createJiti } = await import("jiti");
374
+ const hook = await createJiti(import.meta.url).import(file, { default: true });
375
+ if (typeof hook !== "function") throw new Error(`${file} must export a function as its default export.`);
376
+ await hook(hookArgs, { extensionPoint });
377
+ }
378
+ /**
379
+ * Creates an async App Builder hook that runs the given function and then the
380
+ * user hook file, logging any error and exiting the process with code 1.
381
+ *
382
+ * @param hookName - The hook name, e.g. "pre-app-build".
383
+ * @param extension - The extension the hook is registered for.
384
+ * @param fn - The hook implementation.
385
+ */
386
+ function createHook(hookName, extension, fn) {
387
+ return async (hookArgs) => {
388
+ try {
389
+ const projectRoot = await getProjectRootDirectory();
390
+ await fn(projectRoot);
391
+ await runUserHook(projectRoot, hookName, extension, hookArgs);
392
+ } catch (error) {
393
+ if (error instanceof CommerceSdkValidationError) consola$1.error(error.display());
394
+ else consola$1.error(error);
395
+ process.exit(1);
396
+ }
397
+ };
398
+ }
399
+ /**
400
+ * Creates the `pre-app-build` hook for the given extension.
401
+ * @param extension - The extension the hook is registered for.
402
+ */
403
+ function preAppBuild(extension) {
404
+ return createHook("pre-app-build", extension, (projectRoot) => run$2(extension, projectRoot, TEMPLATES_DIR, { isDevSession }));
405
+ }
406
+ /**
407
+ * Creates the `pre-app-run` hook for the given extension.
408
+ * @param extension - The extension the hook is registered for.
409
+ */
410
+ function preAppRun(extension) {
411
+ return createHook("pre-app-run", extension, (projectRoot) => {
412
+ isDevSession = true;
413
+ return run(projectRoot);
414
+ });
415
+ }
416
+ /**
417
+ * Creates the `pre-app-dev` hook for the given extension.
418
+ * @param extension - The extension the hook is registered for.
419
+ */
420
+ function preAppDev(extension) {
421
+ return createHook("pre-app-dev", extension, (projectRoot) => {
422
+ isDevSession = true;
423
+ return run$1(projectRoot);
424
+ });
425
+ }
426
+ /**
427
+ * Creates the `post-app-deploy` hook for the given extension.
428
+ * @param extension - The extension the hook is registered for.
429
+ */
430
+ function postAppDeploy(extension) {
431
+ return createHook("post-app-deploy", extension, () => run$3());
432
+ }
433
+
434
+ //#endregion
435
+ export { postAppDeploy, preAppBuild, preAppDev, preAppRun };