@adobe/aio-commerce-lib-app 2.0.0 → 2.0.1-alpha-20260923160203

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 +6 -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 +393 -0
  9. package/dist/cjs/cli/index.d.cts +31 -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-DwuJfAiP.cjs} +1 -1
  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-CCzvhSf0.cjs +1515 -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 +31 -0
  27. package/dist/es/cli/index.mjs +381 -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-DPiidKGS.mjs} +1 -1
  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-CM_09qwk.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
@@ -13,1219 +13,19 @@
13
13
  * governing permissions and limitations under the License.
14
14
  */
15
15
 
16
+ import { a as resolveCommerceAppConfig, c as getExecCommand, g as loadPackageJson, h as isESM, i as readCommerceAppConfig, n as isTypeScriptConfig, o as appendCommand, p as getProjectRootDirectory, r as parseCommerceAppConfig, s as detectPackageManager, y as readPackageJson } from "../index.node-DPiidKGS.mjs";
17
+ import { C as COMMERCE_APP_CONFIG_FILE, D as PACKAGE_JSON_FILE, E as INSTALL_YAML_FILE, O as TEMPLATES_DIR, S as BACKEND_UI_V2_EXTENSION_POINT_ID, T as EXTENSIBILITY_EXTENSION_POINT_ID, _ as getExtConfigPath, a as exec$4, b as runInstall, c as run$3, f as updateExtConfig, g as getRuntimeActions, h as readYamlFile, i as syncActionsTypecheckScript, l as generateActionFiles, m as getOrCreateSeq, n as prepareWebSourceImportAlias, o as run$4, p as getOrCreateMap, r as scaffoldTypeScriptProject, s as exec$3, t as generateWebSrc, u as prepareRuntimeAppConfigModule, v as loadAppManifest, w as CONFIGURATION_EXTENSION_POINT_ID, x as APP_CONFIG_FILE, y as prettierFormat } from "../web-src-CM_09qwk.mjs";
18
+ import { l as hasBackendUiV2Components } from "../webhooks-BMBfTqwi.mjs";
19
+ import { t as inspect } from "../logging-DbJHXSKh.mjs";
16
20
  import { t as stringifyError } from "../error-_RMwtrUA.mjs";
17
- import { _ as makeOutputDirFor, a as resolveCommerceAppConfig, c as getExecCommand, d as getPackageExecutionCommand, f as getProjectInstallCommand, g as loadPackageJson, h as isESM, i as readCommerceAppConfig, l as getInstallCommand, m as getRunScriptCommand, n as isTypeScriptConfig, o as appendCommand, p as getProjectRootDirectory, r as parseCommerceAppConfig, s as detectPackageManager, t as hasNamedCommerceAppConfigExports, u as getPackageDependencyInstallPlan, v as mergePackageJsonDependencies, y as readPackageJson } from "../index.node-CW46zuxW.mjs";
18
- import { a as hasCustomInstallationSteps, f as hasBackendUiV2Components } from "../webhooks-CHZ1SXxw.mjs";
19
- import { o as getConfigDomains, t as validateCommerceAppConfig, u as hasBusinessConfigSchema } from "../validate-NzW1FQwL.mjs";
20
- import { n as INSTALLATION_INVOCATION_SOURCE_HEADER, r as POST_APP_DEPLOY_INVOCATION_SOURCE } from "../common-dy6NrZt8.mjs";
21
- import { t as inspect } from "../logging-CXesop3u.mjs";
22
- import { hasDynamicSchema } from "@adobe/aio-commerce-lib-config";
21
+ import { o as getConfigDomains, t as validateCommerceAppConfig, u as hasBusinessConfigSchema } from "../validate-Pd7RIA_3.mjs";
23
22
  import { CommerceSdkValidationError } from "@adobe/aio-commerce-lib-core/error";
24
- import { stringify as stringify$1 } from "safe-stable-stringify";
23
+ import { writeFile } from "fs/promises";
24
+ import { join, relative, resolve } from "path";
25
25
  import consola$1, { consola } from "consola";
26
- import { constants, existsSync, readFileSync, writeFileSync } from "fs";
27
- import { access, copyFile, mkdir, readFile, readdir, rm, writeFile } from "fs/promises";
28
- import path, { basename, dirname, extname, join, relative, resolve, sep } from "path";
29
- import { colorize, colors, formatTree } from "consola/utils";
30
- import { fileURLToPath } from "url";
31
- import { execSync, spawnSync } from "child_process";
32
- import * as prettier from "prettier";
33
- import * as v from "valibot";
34
- import { Document, YAMLMap, YAMLSeq, isMap, isPair, isSeq, parseDocument } from "yaml";
35
- import aioIms from "@adobe/aio-lib-ims";
36
- import config from "@adobe/aio-lib-core-config";
37
- import ky, { HTTPError } from "ky";
38
- import { setTimeout } from "timers/promises";
39
- import dotenv from "dotenv";
26
+ import { colorize } from "consola/utils";
27
+ import { isMap } from "yaml";
40
28
 
41
- //#region source/commands/constants.ts
42
- /** The name of the package containing all the generated runtime actions */
43
- const PACKAGE_NAME = "app-management";
44
- /** The path to the directory containing the generated files */
45
- const GENERATED_PATH = ".generated";
46
- /** The folder name containing the runtime actions */
47
- const ACTIONS_FOLDER = `actions/${PACKAGE_NAME}`;
48
- /** The path to the directory containing the generated actions */
49
- const GENERATED_ACTIONS_PATH = `${GENERATED_PATH}/${ACTIONS_FOLDER}`;
50
- /** The name of the extension point for extensibility. */
51
- const EXTENSIBILITY_EXTENSION_POINT_ID = "commerce/extensibility/1";
52
- /** The name of the extension point for configuration. */
53
- const CONFIGURATION_EXTENSION_POINT_ID = "commerce/configuration/1";
54
- /** The name of the extension point for Admin UI. */
55
- const BACKEND_UI_V2_EXTENSION_POINT_ID = "commerce/backend-ui/2";
56
- /** The name of the configuration schema file */
57
- const APP_MANIFEST_FILE = "app.commerce.manifest.json";
58
- /** The name of the generated runtime-safe app config module (dynamic schemas only). */
59
- const RUNTIME_APP_CONFIG_FILE = "app.commerce.config.js";
60
- /** Package import alias for the generated runtime-safe app config module. */
61
- const APP_CONFIG_IMPORT_ALIAS = "#app.commerce.config";
62
- /** The name of the commerce app configuration file */
63
- const COMMERCE_APP_CONFIG_FILE = "app.commerce.config";
64
- /** TypeScript development dependencies shared by root and web-src scaffolding. */
65
- const SHARED_TYPESCRIPT_DEV_DEPENDENCIES = [{
66
- name: "@tsconfig/bases",
67
- version: "^1.0.26"
68
- }, {
69
- name: "typescript",
70
- version: "^6.0.3"
71
- }];
72
- /** Node.js type definitions required only by root Runtime action scaffolding. */
73
- const TYPESCRIPT_NODE_DEV_DEPENDENCY = {
74
- name: "@types/node",
75
- version: "^24.12.0"
76
- };
77
- /** TypeScript loader required only by root Runtime action scaffolding. */
78
- const TYPESCRIPT_WEBPACK_DEV_DEPENDENCY = {
79
- name: "ts-loader",
80
- version: "^9.6.2"
81
- };
82
- /** The name of the configuration schema file */
83
- const CONFIG_SCHEMA_FILE_NAME = "configuration-schema.json";
84
- /** The name of the project package file */
85
- const PACKAGE_JSON_FILE = "package.json";
86
- /** The name of the app configuration file */
87
- const APP_CONFIG_FILE = "app.config.yaml";
88
- /** The name of the install configuration file */
89
- const INSTALL_YAML_FILE = "install.yaml";
90
- /** Get the path to the extension point folder */
91
- function getExtensionPointFolderPath(extensionPointId) {
92
- return `src/${extensionPointId.replaceAll("/", "-")}`;
93
- }
94
-
95
- //#endregion
96
- //#region source/commands/generate/actions/constants.ts
97
- const __filename = fileURLToPath(import.meta.url);
98
- const __dirname = dirname(__filename);
99
- /** The path to the action templates directory, sibling to this file. */
100
- const TEMPLATES_DIR = join(__dirname, "templates");
101
- /** The list of Commerce variables that are required for the runtime actions. */
102
- const COMMERCE_VARIABLES = [
103
- "AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
104
- "AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
105
- "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID",
106
- "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
107
- "AIO_COMMERCE_AUTH_IMS_ORG_ID",
108
- "AIO_COMMERCE_AUTH_IMS_SCOPES"
109
- ];
110
- /** The inputs for the generated runtime actions. */
111
- const COMMERCE_ACTION_INPUTS = Object.fromEntries(COMMERCE_VARIABLES.map((variable) => [variable, `$${variable}`]));
112
- const CUSTOM_IMPORTS_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_IMPORTS}}";
113
- const CUSTOM_SCRIPTS_MAP_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_MAP}}";
114
- const CUSTOM_SCRIPTS_LOADER_PLACEHOLDER = "// {{CUSTOM_SCRIPTS_LOADER}}";
115
-
116
- //#endregion
117
- //#region source/commands/utils.ts
118
- /** Format file content using prettier, inferring the parser from the file path. */
119
- function prettierFormat(content, filepath) {
120
- return prettier.format(content, {
121
- arrowParens: "always",
122
- bracketSameLine: true,
123
- bracketSpacing: true,
124
- filepath,
125
- printWidth: 80,
126
- quoteStyle: "double",
127
- semi: true,
128
- tabWidth: 2,
129
- trailingComma: "all",
130
- useTabs: false
131
- });
132
- }
133
- /**
134
- * Load the app commerce config.
135
- * @param projectRoot - Resolved project root containing the app configuration.
136
- */
137
- async function loadAppManifest(projectRoot) {
138
- const appConfig = await parseCommerceAppConfig(projectRoot);
139
- consola$1.debug("Loaded app commerce config");
140
- return appConfig;
141
- }
142
- /**
143
- * Install the given dependencies.
144
- * @param packageManager - The detected package manager.
145
- * @param dependencies - Package specifiers to install; no-op when empty.
146
- * @param cwd - Working directory for the install command.
147
- * @param options - Install command options.
148
- */
149
- function runInstall(packageManager, dependencies, cwd = process.cwd(), options = {}) {
150
- if (dependencies.length === 0) return;
151
- const dependencyListString = dependencies.map((dependency) => ` - ${dependency}`).join("\n");
152
- consola$1.start([`Installing the following dependencies with ${packageManager}:\n${dependencyListString}\n`, "This may take a few seconds...\n"].join("\n"));
153
- const { command, args } = getInstallCommand(packageManager, dependencies, options);
154
- const displayCommand = [command, ...args].join(" ");
155
- const result = spawnSync(command, args, {
156
- cwd,
157
- stdio: "inherit"
158
- });
159
- 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}`) });
160
- consola$1.log("");
161
- consola$1.success("Dependencies installed successfully");
162
- }
163
- /**
164
- * Install dependencies declared in package.json.
165
- * @param packageManager - The detected package manager.
166
- * @param cwd - Working directory for the install command.
167
- */
168
- function runProjectInstall(packageManager, cwd = process.cwd()) {
169
- consola$1.start([`Installing project dependencies with ${packageManager}...`, "This may take a few seconds...\n"].join("\n"));
170
- const { command, args } = getProjectInstallCommand(packageManager);
171
- const displayCommand = [command, ...args].join(" ");
172
- const result = spawnSync(command, args, {
173
- cwd,
174
- stdio: "inherit"
175
- });
176
- 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}`) });
177
- consola$1.log("");
178
- consola$1.success("Dependencies installed successfully");
179
- }
180
- /**
181
- * Whether the app config contains anything that can't be JSON-serialized as-is
182
- * and therefore requires generating a runtime-safe ESM config module.
183
- */
184
- function hasDynamicAppConfig(appConfig) {
185
- const schema = appConfig.businessConfig?.schema;
186
- return Array.isArray(schema) && hasDynamicSchema(schema);
187
- }
188
- /**
189
- * Whether preserving the app config requires generating a JavaScript module.
190
- * @param appConfig - The parsed app config.
191
- * @param projectRoot - Resolved project root containing the source config.
192
- */
193
- async function requiresJavaScriptAppConfig(appConfig, projectRoot) {
194
- return hasDynamicAppConfig(appConfig) || await hasNamedCommerceAppConfigExports(projectRoot);
195
- }
196
- /**
197
- * Path to an extension point's `.generated` directory, relative to the project root.
198
- * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
199
- */
200
- function getGeneratedDir(extensionPointId) {
201
- return join(getExtensionPointFolderPath(extensionPointId), GENERATED_PATH);
202
- }
203
- /**
204
- * Path to an extension point's generated actions directory, relative to the project root.
205
- * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
206
- */
207
- function getActionsDir(extensionPointId) {
208
- return join(getExtensionPointFolderPath(extensionPointId), GENERATED_ACTIONS_PATH);
209
- }
210
- /**
211
- * Path to a specific generated action file, relative to the project root.
212
- * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
213
- * @param actionName - The name of the action, e.g. "my-action"
214
- */
215
- function getActionPath(extensionPointId, actionName) {
216
- return join(getActionsDir(extensionPointId), `${actionName}.js`);
217
- }
218
- /**
219
- * Path to an extension point's `ext.config.yaml`, relative to the project root.
220
- * @param extensionPointId - The extension point ID, e.g. "commerce/extensibility/1"
221
- */
222
- function getExtConfigPath(extensionPointId) {
223
- return join(getExtensionPointFolderPath(extensionPointId), "ext.config.yaml");
224
- }
225
- /** Path to the generated app manifest JSON file, relative to the project root. */
226
- function getManifestPath() {
227
- return join(getGeneratedDir(EXTENSIBILITY_EXTENSION_POINT_ID), APP_MANIFEST_FILE);
228
- }
229
- /**
230
- * Path to the generated runtime-safe app config ESM module, relative to the
231
- * project root. Used when the business config schema contains dynamic options.
232
- */
233
- function getRuntimeAppConfigPath() {
234
- return join(getGeneratedDir(EXTENSIBILITY_EXTENSION_POINT_ID), RUNTIME_APP_CONFIG_FILE);
235
- }
236
- /** Path to the generated configuration schema file, relative to the project root. */
237
- function getSchemaPath() {
238
- return join(getGeneratedDir(CONFIGURATION_EXTENSION_POINT_ID), CONFIG_SCHEMA_FILE_NAME);
239
- }
240
-
241
- //#endregion
242
- //#region source/commands/typescript.ts
243
- const WEBPACK_CONFIG_PATTERN = /^webpack-config\.(?:cjs|js)$/u;
244
- const WEBPACK_CONFIG_FILE = "webpack-config.cjs";
245
- const TSCONFIG_FILE = "tsconfig.json";
246
- const ROOT_TYPESCRIPT_CONFIG = {
247
- compilerOptions: {
248
- allowJs: true,
249
- checkJs: true,
250
- module: "esnext",
251
- moduleResolution: "bundler",
252
- noEmit: true
253
- },
254
- exclude: ["src/**/web-src/**"],
255
- extends: [
256
- "@tsconfig/bases/recommended",
257
- "@tsconfig/bases/node-ts",
258
- "@tsconfig/bases/node24"
259
- ],
260
- include: [
261
- "src/**/.generated/actions/**/*.js",
262
- "app.commerce.config.ts",
263
- "app.commerce.config.mts",
264
- "app.commerce.config.cts",
265
- "src/**/*.ts",
266
- "src/**/*.mts",
267
- "src/**/*.cts"
268
- ]
269
- };
270
- const TYPESCRIPT_DEV_DEPENDENCIES = [
271
- ...SHARED_TYPESCRIPT_DEV_DEPENDENCIES,
272
- TYPESCRIPT_NODE_DEV_DEPENDENCY,
273
- TYPESCRIPT_WEBPACK_DEV_DEPENDENCY
274
- ];
275
- const ACTIONS_TYPECHECK_SCRIPT = "typecheck:actions";
276
- const WEB_SOURCE_TYPECHECK_SCRIPT = "typecheck:web-src";
277
- const ACTIONS_TYPECHECK_COMMAND = "tsc --noEmit -p tsconfig.json";
278
- const WEB_SOURCE_TYPECHECK_COMMAND = `tsc --noEmit -p ${`${getExtensionPointFolderPath(BACKEND_UI_V2_EXTENSION_POINT_ID)}/web-src/tsconfig.json`}`;
279
- /**
280
- * Create the default Webpack configuration when the project has none.
281
- * @param projectRoot Project root directory.
282
- */
283
- async function scaffoldWebpackConfig(projectRoot) {
284
- const existingConfig = (await readdir(projectRoot, { withFileTypes: true })).find((entry) => entry.isFile() && WEBPACK_CONFIG_PATTERN.test(entry.name));
285
- if (existingConfig !== void 0) {
286
- consola.info(`Found ${existingConfig.name}; leaving it unchanged.`);
287
- return;
288
- }
289
- await copyFile(join(TEMPLATES_DIR, "typescript", WEBPACK_CONFIG_FILE), join(projectRoot, WEBPACK_CONFIG_FILE));
290
- }
291
- /**
292
- * Create the Runtime actions TypeScript configuration when it does not exist.
293
- * @param projectRoot Project root directory.
294
- */
295
- async function scaffoldTypeScriptConfig(projectRoot) {
296
- const rootConfigPath = join(projectRoot, TSCONFIG_FILE);
297
- if (!existsSync(rootConfigPath)) await writeFile(rootConfigPath, `${JSON.stringify(ROOT_TYPESCRIPT_CONFIG, null, 2)}\n`, "utf-8");
298
- }
299
- /**
300
- * Install missing TypeScript tooling after validating installed versions.
301
- * @param packageManager Package manager used by the project.
302
- * @param projectRoot Project root directory.
303
- */
304
- async function installTypeScriptDependencies(packageManager, projectRoot) {
305
- const installPlan = await getPackageDependencyInstallPlan(TYPESCRIPT_DEV_DEPENDENCIES, projectRoot);
306
- if (installPlan.incompatible.length > 0) {
307
- const dependencies = installPlan.incompatible.map(({ name, version, installedVersion }) => `${name}@${installedVersion} does not satisfy ${version}`).join("\n");
308
- throw new Error(`Cannot configure TypeScript because installed dependencies are incompatible:\n${dependencies}`);
309
- }
310
- runInstall(packageManager, installPlan.missing.map(({ name, version }) => `${name}@${version}`), projectRoot, { dev: true });
311
- }
312
- /**
313
- * Scaffold the files and dependencies for a new TypeScript Commerce project.
314
- * @param packageManager Package manager used by the initialized project.
315
- * @param projectRoot Resolved project root.
316
- */
317
- async function scaffoldTypeScriptProject(packageManager, projectRoot) {
318
- await scaffoldWebpackConfig(projectRoot);
319
- await scaffoldTypeScriptConfig(projectRoot);
320
- await installTypeScriptDependencies(packageManager, projectRoot);
321
- }
322
- /**
323
- * Add a generated typecheck script and compose it into the root typecheck script.
324
- * @param scriptName Generated package script name.
325
- * @param command TypeScript command run by the generated script.
326
- * @param projectRoot Resolved project root.
327
- */
328
- async function syncTypecheckScript(scriptName, command, projectRoot) {
329
- const pkg = await loadPackageJson(projectRoot);
330
- if (pkg === null) throw new Error("Could not find package.json.");
331
- const scripts = {
332
- ...pkg.content.scripts,
333
- [scriptName]: command
334
- };
335
- const packageManager = await detectPackageManager(projectRoot);
336
- const typecheck = appendCommand(scripts.typecheck, getRunScriptCommand(packageManager, scriptName));
337
- pkg.update({ scripts: {
338
- ...scripts,
339
- typecheck
340
- } });
341
- await pkg.save();
342
- }
343
- /**
344
- * Add the Runtime actions typecheck script to a project.
345
- * @param projectRoot Resolved project root.
346
- */
347
- async function syncActionsTypecheckScript(projectRoot) {
348
- await syncTypecheckScript(ACTIONS_TYPECHECK_SCRIPT, ACTIONS_TYPECHECK_COMMAND, projectRoot);
349
- }
350
- /**
351
- * Add the web-src typecheck script to a project.
352
- * @param projectRoot Resolved project root.
353
- */
354
- async function syncWebSourceTypecheckScript(projectRoot) {
355
- await syncTypecheckScript(WEB_SOURCE_TYPECHECK_SCRIPT, WEB_SOURCE_TYPECHECK_COMMAND, projectRoot);
356
- }
357
-
358
- //#endregion
359
- //#region source/commands/generate/web-src.ts
360
- const JSX_FILE_EXTENSION = ".jsx";
361
- const TSX_FILE_EXTENSION = ".tsx";
362
- const APP_TITLE_PLACEHOLDER = "APP_TITLE";
363
- const WEB_SOURCE_ENTRYPOINT_FILE = "index.html";
364
- const WEB_SOURCE_IMPORT_ALIAS = "#web/*";
365
- const WEB_SOURCE_REQUIRED_FILES = [".babelrc"];
366
- const WEB_SOURCE_TSCONFIG_FILE = "tsconfig.json";
367
- const LEADING_DOT_SLASH_PATTERN$1 = /^\.\//u;
368
- /** Runtime dependencies required by the generated web-src app. */
369
- const WEB_SOURCE_DEPENDENCIES = [
370
- {
371
- name: "@adobe/aio-commerce-lib-admin-ui",
372
- version: "^1.1.0"
373
- },
374
- {
375
- name: "react",
376
- version: "^19.2.7"
377
- },
378
- {
379
- name: "react-dom",
380
- version: "^19.2.7"
381
- },
382
- {
383
- name: "@react-spectrum/s2",
384
- version: "^1.5.1"
385
- }
386
- ];
387
- const WEB_SOURCE_BABEL_DEV_DEPENDENCIES = [{
388
- name: "@babel/core",
389
- version: "^7.29.7"
390
- }, {
391
- name: "@babel/preset-react",
392
- version: "^7.29.7"
393
- }];
394
- /** Development dependencies required by the generated web-src app. */
395
- const WEB_SOURCE_DEV_DEPENDENCIES = [
396
- ...WEB_SOURCE_BABEL_DEV_DEPENDENCIES,
397
- {
398
- name: "@types/react",
399
- version: "^19.2.17"
400
- },
401
- {
402
- name: "@types/react-dom",
403
- version: "^19.2.3"
404
- }
405
- ];
406
- const WEB_SOURCE_REQUIRED_DEV_DEPENDENCIES = WEB_SOURCE_BABEL_DEV_DEPENDENCIES;
407
- const WEB_SOURCE_SHARED_BUNDLES = [{
408
- assets: ["**/@react-spectrum/s2/**", "src/commerce-backend-ui-2/web-src/*.{js,jsx,ts,tsx}"],
409
- name: "s2-styles",
410
- types: ["css"]
411
- }];
412
- const WEB_SOURCE_TYPESCRIPT_CONFIG = {
413
- compilerOptions: {
414
- allowImportingTsExtensions: true,
415
- allowJs: true,
416
- checkJs: true,
417
- jsx: "react-jsx",
418
- jsxImportSource: "react",
419
- module: "esnext",
420
- moduleResolution: "bundler",
421
- noEmit: true,
422
- rewriteRelativeImportExtensions: false
423
- },
424
- extends: ["@tsconfig/bases/recommended"],
425
- include: [
426
- "src/**/*.js",
427
- "src/**/*.ts",
428
- "src/**/*.tsx",
429
- "src/**/*.jsx"
430
- ]
431
- };
432
- /** Normalize a generated file path for package.json imports. */
433
- function normalizePackageJsonPath$1(path) {
434
- const normalizedPath = path.split(sep).join("/");
435
- return (normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`).replace(LEADING_DOT_SLASH_PATTERN$1, "./");
436
- }
437
- /**
438
- * Resolve the generated web-src entrypoint path from an extension config.
439
- * @param extConfig - Extension config containing the view operation.
440
- */
441
- function getWebSourceEntrypoint(extConfig) {
442
- const viewEntrypoint = extConfig.operations?.view?.[0]?.impl;
443
- if (viewEntrypoint === void 0) return null;
444
- return join(getExtensionPointFolderPath(BACKEND_UI_V2_EXTENSION_POINT_ID), extConfig.web ?? "web-src", viewEntrypoint);
445
- }
446
- /**
447
- * Ensure package.json has the dependencies and Parcel config needed by web-src.
448
- * @param projectRoot - Resolved project root containing package.json.
449
- * @param extension - Web source extension whose tooling is configured.
450
- */
451
- async function prepareWebSourcePackage(projectRoot, extension, scaffoldRequired) {
452
- const pkg = await loadPackageJson(projectRoot);
453
- if (pkg === null) throw new Error("Could not find package.json.");
454
- const dependencies = scaffoldRequired ? WEB_SOURCE_DEPENDENCIES : [];
455
- const requiredDevDependencies = scaffoldRequired ? [...WEB_SOURCE_DEV_DEPENDENCIES, ...extension === "tsx" ? SHARED_TYPESCRIPT_DEV_DEPENDENCIES : []] : WEB_SOURCE_REQUIRED_DEV_DEPENDENCIES;
456
- const installPlan = await getPackageDependencyInstallPlan([...dependencies, ...requiredDevDependencies], projectRoot);
457
- if (installPlan.incompatible.length > 0) {
458
- const incompatibleDependencies = installPlan.incompatible.map(({ name, version, installedVersion }) => `${name}@${installedVersion} does not satisfy ${version}`).join("\n");
459
- throw new Error(`Cannot scaffold web-src because installed dependencies are incompatible:\n${incompatibleDependencies}`);
460
- }
461
- const existingDependencies = pkg.content.dependencies ?? {};
462
- const devDependencies = pkg.content.devDependencies ?? {};
463
- const dependencyMaps = [existingDependencies, devDependencies];
464
- const declaredDependencyNames = new Set(dependencyMaps.flatMap((depMap) => Object.keys(depMap)));
465
- const dependenciesToDeclare = [...dependencies, ...requiredDevDependencies].filter(({ name }) => !declaredDependencyNames.has(name));
466
- if (dependenciesToDeclare.length > 0) {
467
- consola.info("Adding web-src dependencies in package.json:");
468
- consola.log.raw(formatTree(dependenciesToDeclare.map(({ name, version }) => ` ${name}@${version}`)));
469
- } else consola.info("web-src dependencies are already declared in package.json.");
470
- pkg.update({
471
- ...scaffoldRequired && {
472
- "@parcel/bundler-default": {
473
- ...pkg.content["@parcel/bundler-default"],
474
- manualSharedBundles: WEB_SOURCE_SHARED_BUNDLES
475
- },
476
- "@parcel/resolver-default": {
477
- ...pkg.content["@parcel/resolver-default"],
478
- packageExports: true
479
- },
480
- dependencies: mergePackageJsonDependencies(existingDependencies, WEB_SOURCE_DEPENDENCIES, dependencyMaps)
481
- },
482
- devDependencies: mergePackageJsonDependencies(devDependencies, requiredDevDependencies, dependencyMaps)
483
- });
484
- await pkg.save();
485
- if (installPlan.missing.length === 0) {
486
- consola.info("web-src dependencies are already installed.");
487
- return;
488
- }
489
- consola.info("Installing missing web-src dependencies from package.json:");
490
- consola.log.raw(formatTree(installPlan.missing.map(({ name, version }) => ` ${name}@${version}`)));
491
- const packageManager = await detectPackageManager(projectRoot);
492
- runProjectInstall(packageManager, projectRoot);
493
- }
494
- /**
495
- * Copies required web-src support files that are not already present.
496
- * @param sourceDir - Directory containing required web source files.
497
- * @param targetDir - Generated or existing web source directory.
498
- * @param projectRoot - Resolved project root used to format output paths.
499
- */
500
- async function copyMissingWebSourceFiles(sourceDir, targetDir, projectRoot) {
501
- return (await Promise.all(WEB_SOURCE_REQUIRED_FILES.map(async (file) => {
502
- const targetPath = join(targetDir, file);
503
- if (existsSync(targetPath)) return null;
504
- await copyFile(join(sourceDir, file), targetPath, constants.COPYFILE_EXCL);
505
- return ` ${relative(projectRoot, targetPath)}`;
506
- }))).filter((path) => path !== null);
507
- }
508
- /**
509
- * Add the package import alias for an existing or generated web-src.
510
- * @param extConfig - Extension config containing the view operation.
511
- * @param projectRoot - Resolved project root containing package.json.
512
- */
513
- async function prepareWebSourceImportAlias(extConfig, projectRoot) {
514
- const entrypoint = getWebSourceEntrypoint(extConfig);
515
- if (entrypoint === null) return;
516
- const pkg = await loadPackageJson(projectRoot);
517
- if (pkg === null) throw new Error("Could not find package.json.");
518
- const existingImports = typeof pkg.content.imports === "object" && pkg.content.imports !== null ? pkg.content.imports : {};
519
- const webSourceSrcPath = join(dirname(entrypoint), "src");
520
- pkg.update({ imports: {
521
- ...existingImports,
522
- [WEB_SOURCE_IMPORT_ALIAS]: `${normalizePackageJsonPath$1(webSourceSrcPath)}/*`
523
- } });
524
- await pkg.save();
525
- }
526
- /** Resolve the generated web-src file extension from the app config file type. */
527
- async function resolveWebSourceExtension(projectRoot) {
528
- const configFilePath = await resolveCommerceAppConfig(projectRoot);
529
- return configFilePath !== null && isTypeScriptConfig(configFilePath) ? "tsx" : "jsx";
530
- }
531
- async function writeWebSourceTypeScriptConfig(targetDir) {
532
- const tsconfigPath = join(targetDir, WEB_SOURCE_TSCONFIG_FILE);
533
- await writeFile(tsconfigPath, `${JSON.stringify(WEB_SOURCE_TYPESCRIPT_CONFIG, null, 2)}\n`, "utf-8");
534
- return tsconfigPath;
535
- }
536
- /**
537
- * Resolve the destination path for a web-src template file.
538
- * @param templatePath - Template file path relative to its source directory.
539
- * @param extension - Web source extension to generate.
540
- */
541
- function getWebSourceTemplateTargetPath(templatePath, extension) {
542
- if (extension === "jsx" || !templatePath.endsWith(JSX_FILE_EXTENSION)) return templatePath;
543
- return `${templatePath.slice(0, -4)}${TSX_FILE_EXTENSION}`;
544
- }
545
- /**
546
- * Copy web-src templates directly to the final file extension and entrypoint.
547
- * @param sourceDir - Template web-src directory.
548
- * @param targetDir - Generated web-src directory.
549
- * @param extension - Web source extension to generate.
550
- * @param appTitle - Application title used by the entrypoint template.
551
- * @param projectRoot - Resolved project root used to format output paths.
552
- */
553
- async function copyWebSourceTemplates(sourceDir, targetDir, extension, appTitle, projectRoot) {
554
- await mkdir(targetDir, { recursive: true });
555
- const entries = await readdir(sourceDir, { withFileTypes: true });
556
- return (await Promise.all(entries.map(async (entry) => {
557
- const sourcePath = join(sourceDir, entry.name);
558
- const targetPath = join(targetDir, getWebSourceTemplateTargetPath(entry.name, extension));
559
- if (entry.isDirectory()) return await copyWebSourceTemplates(sourcePath, targetPath, extension, appTitle, projectRoot);
560
- let content = await readFile(sourcePath, "utf-8");
561
- if (extension === "tsx") content = content.replaceAll(`${JSX_FILE_EXTENSION}"`, `${TSX_FILE_EXTENSION}"`);
562
- if (targetPath.endsWith(WEB_SOURCE_ENTRYPOINT_FILE)) content = content.replaceAll(APP_TITLE_PLACEHOLDER, appTitle);
563
- await writeFile(targetPath, content, {
564
- encoding: "utf-8",
565
- flag: "wx"
566
- });
567
- return ` ${relative(projectRoot, targetPath)}`;
568
- }))).flat();
569
- }
570
- /**
571
- * Generates the web source scaffold for an iframe-based Admin UI extension.
572
- * @param extConfig - Extension config containing the web entrypoint.
573
- * @param appName - Application name inserted into the generated entrypoint.
574
- * @param projectRoot - Resolved project root where web source is generated.
575
- * @param templatesDir - Directory containing web source templates.
576
- */
577
- async function generateWebSrc(extConfig, appName, projectRoot, templatesDir = TEMPLATES_DIR) {
578
- const entrypoint = getWebSourceEntrypoint(extConfig);
579
- if (entrypoint === null) return;
580
- const entrypointPath = join(projectRoot, entrypoint);
581
- const scaffoldRequired = !existsSync(entrypointPath);
582
- const targetDir = dirname(entrypointPath);
583
- const extension = await resolveWebSourceExtension(projectRoot);
584
- const sourceDir = join(templatesDir, "admin-ui", "web-src");
585
- let outputFiles = [];
586
- if (scaffoldRequired) {
587
- consola.start(`Scaffolding web-src for ${BACKEND_UI_V2_EXTENSION_POINT_ID}...`);
588
- outputFiles = await copyWebSourceTemplates(sourceDir, targetDir, extension, appName, projectRoot);
589
- if (extension === "tsx") {
590
- const tsconfigPath = await writeWebSourceTypeScriptConfig(targetDir);
591
- outputFiles.push(` ${relative(projectRoot, tsconfigPath)}`);
592
- await syncWebSourceTypecheckScript(projectRoot);
593
- }
594
- } else consola.info(`web-src entrypoint already exists, skipping scaffold: ${relative(projectRoot, entrypointPath)}`);
595
- const requiredFiles = await copyMissingWebSourceFiles(sourceDir, targetDir, projectRoot);
596
- if (scaffoldRequired || requiredFiles.length > 0) await prepareWebSourcePackage(projectRoot, extension, scaffoldRequired);
597
- if (scaffoldRequired) {
598
- consola.success(`Scaffolded ${relative(projectRoot, targetDir)}`);
599
- consola.log.raw(formatTree([...outputFiles, ...requiredFiles]));
600
- } else if (requiredFiles.length > 0) {
601
- consola.success(`Added required files to ${relative(projectRoot, targetDir)}`);
602
- consola.log.raw(formatTree(requiredFiles));
603
- }
604
- }
605
-
606
- //#endregion
607
- //#region source/commands/generate/actions/config.ts
608
- /**
609
- * Creates a runtime action configuration.
610
- * @param actionName - The name of the action.
611
- * @param config - Action generation options.
612
- * @param options - Optional configuration options.
613
- */
614
- function createActionDefinition(actionName, config = {}, options = {}) {
615
- const def = {
616
- ...options,
617
- annotations: {
618
- final: true,
619
- "require-adobe-auth": true
620
- },
621
- function: `${GENERATED_ACTIONS_PATH}/${actionName}.js`,
622
- runtime: "nodejs:24",
623
- web: options.web ?? "yes"
624
- };
625
- if (config.requiresEncryptionKey) def.inputs = {
626
- ...def.inputs,
627
- AIO_COMMERCE_CONFIG_ENCRYPTION_KEY: "$AIO_COMMERCE_CONFIG_ENCRYPTION_KEY"
628
- };
629
- return def;
630
- }
631
- /**
632
- * Gets the runtime actions to be generated from the ext.config.yaml configuration.
633
- * @param extConfig - The ext.config.yaml configuration.
634
- * @param dir - Directory containing the runtime action templates.
635
- */
636
- function getRuntimeActions(extConfig, dir) {
637
- return Object.entries(extConfig.runtimeManifest?.packages?.["app-management"]?.actions ?? {}).map(([name]) => ({
638
- name,
639
- templateFile: join(dir, `${name}.js.template`)
640
- }));
641
- }
642
- /**
643
- * Builds the ext.config.yaml configuration for the extensibility extension.
644
- * @param appConfig - Parsed app configuration.
645
- */
646
- function buildAppManagementExtConfig(appConfig) {
647
- const extConfig = {
648
- hooks: {
649
- "post-app-deploy": "EXTENSION=extensibility/1 $packageExec aio-commerce-lib-app hooks post-app-deploy",
650
- "pre-app-build": "EXTENSION=extensibility/1 $packageExec aio-commerce-lib-app hooks pre-app-build"
651
- },
652
- operations: { workerProcess: [{
653
- impl: `${PACKAGE_NAME}/app-config`,
654
- type: "action"
655
- }, {
656
- impl: `${PACKAGE_NAME}/association`,
657
- type: "action"
658
- }] },
659
- runtimeManifest: { packages: { [PACKAGE_NAME]: {
660
- actions: {
661
- "app-config": createActionDefinition("app-config"),
662
- association: createActionDefinition("association")
663
- },
664
- license: "Apache-2.0"
665
- } } }
666
- };
667
- const hasPasswordFieldsInSchema = hasBusinessConfigSchema(appConfig) && appConfig.businessConfig.schema.some((field) => field.type === "password");
668
- extConfig.operations.workerProcess.push({
669
- impl: `${PACKAGE_NAME}/installation`,
670
- type: "action"
671
- });
672
- extConfig.runtimeManifest.packages[PACKAGE_NAME].actions.installation = createActionDefinition("installation", { requiresEncryptionKey: hasPasswordFieldsInSchema }, {
673
- inputs: {
674
- ...COMMERCE_ACTION_INPUTS,
675
- LOG_LEVEL: "$LOG_LEVEL"
676
- },
677
- limits: { timeout: 6e5 }
678
- });
679
- return extConfig;
680
- }
681
- /** Builds the ext.config.yaml configuration for the business configuration extension. */
682
- function buildBusinessConfigurationExtConfig() {
683
- const actions = [{
684
- name: "config",
685
- requiresEncryptionKey: true,
686
- templateFile: "config.js.template"
687
- }, {
688
- name: "scope-tree",
689
- templateFile: "scope-tree.js.template"
690
- }];
691
- return {
692
- hooks: { "pre-app-build": "EXTENSION=configuration/1 $packageExec aio-commerce-lib-app hooks pre-app-build" },
693
- operations: { workerProcess: actions.map((action) => ({
694
- impl: `${PACKAGE_NAME}/${action.name}`,
695
- type: "action"
696
- })) },
697
- runtimeManifest: { packages: { [PACKAGE_NAME]: {
698
- actions: Object.fromEntries(actions.map((action) => [action.name, createActionDefinition(action.name, action)])),
699
- license: "Apache-2.0"
700
- } } }
701
- };
702
- }
703
- /** Collects unique `runtimeAction` strings from all worker entries in an `adminUi` config. */
704
- function collectUniqueRuntimeActions(adminUi) {
705
- const entities = [
706
- "order",
707
- "product",
708
- "customer"
709
- ].map((key) => adminUi?.[key]);
710
- const gridRuntimeActions = entities.map((entity) => entity?.gridColumns?.runtimeAction).filter((action) => action !== void 0);
711
- const massActionRuntimeActions = entities.flatMap((entity) => entity?.massActions ?? []).filter((action) => action.type === "worker").map((action) => action.runtimeAction);
712
- const viewButtonRuntimeActions = (adminUi?.order?.viewButtons ?? []).filter((button) => button.type === "worker").map((button) => button.runtimeAction);
713
- return [.../* @__PURE__ */ new Set([
714
- ...gridRuntimeActions,
715
- ...massActionRuntimeActions,
716
- ...viewButtonRuntimeActions
717
- ])];
718
- }
719
- /** Returns true if the `adminUi` config requires a `view` operation and `web` source in ext.config.yaml. */
720
- function requiresWebSource(adminUi) {
721
- if (adminUi?.menu !== void 0) return true;
722
- if ((adminUi?.order?.viewButtons ?? []).some((button) => button.type === "view")) return true;
723
- return [
724
- "order",
725
- "product",
726
- "customer"
727
- ].map((key) => adminUi?.[key]).flatMap((entity) => entity?.massActions ?? []).some((action) => action.type === "view");
728
- }
729
- /**
730
- * Builds the ext.config.yaml for the Admin UI v2 extension (`commerce/backend-ui/2`).
731
- * Derives `workerProcess` and `view` operation declarations from `adminUi` config.
732
- * Adds a `view` operation and `web` source when view-type entries or `adminUi.menu` is configured.
733
- * @param appConfig - Parsed app configuration.
734
- */
735
- function buildAdminUiV2ExtConfig(appConfig) {
736
- const { adminUi } = appConfig;
737
- const runtimeActions = collectUniqueRuntimeActions(adminUi);
738
- const requiresWeb = requiresWebSource(adminUi);
739
- return {
740
- hooks: {
741
- "pre-app-build": "EXTENSION=backend-ui/2 $packageExec aio-commerce-lib-app hooks pre-app-build",
742
- "pre-app-dev": "EXTENSION=backend-ui/2 $packageExec aio-commerce-lib-app hooks pre-app-dev",
743
- "pre-app-run": "EXTENSION=backend-ui/2 $packageExec aio-commerce-lib-app hooks pre-app-run"
744
- },
745
- operations: {
746
- ...requiresWeb && { view: [{
747
- impl: "index.html",
748
- type: "web"
749
- }] },
750
- ...runtimeActions.length > 0 && { workerProcess: runtimeActions.map((impl) => ({
751
- impl,
752
- type: "action"
753
- })) }
754
- },
755
- ...requiresWeb && { web: "web-src" }
756
- };
757
- }
758
-
759
- //#endregion
760
- //#region ../../packages-private/scripting-utils/source/yaml/helpers.ts
761
- /**
762
- * Read a YAML file and return a {@link Document}
763
- * @param path - The path to the YAML file
764
- */
765
- async function readYamlFile(path) {
766
- let doc = new Document();
767
- if (existsSync(path)) try {
768
- const fileContent = await readFile(path, "utf-8");
769
- doc = parseDocument(fileContent, { keepSourceTokens: true });
770
- } catch (err) {
771
- const file = basename(path);
772
- throw new Error(`Failed to parse ${file}`, { cause: err });
773
- }
774
- if (doc.contents === null) doc.contents = new YAMLMap();
775
- return doc;
776
- }
777
- /**
778
- * Generic helper to get or create a YAML node at the given path
779
- * @param doc - The YAML document
780
- * @param path - The path to the node
781
- * @param options - The options for the node
782
- * @param typeConfig - Configuration for the node type
783
- * @returns The existing or newly created node
784
- */
785
- function getOrCreateNode(doc, path, options, typeConfig) {
786
- const node = doc.getIn(path);
787
- if (node) {
788
- if (!typeConfig.typeGuard(node)) throw new Error(`Expected ${typeConfig.typeName} at path "${path.join(".")}".`);
789
- return node;
790
- }
791
- if (doc.hasIn(path)) doc.deleteIn(path);
792
- const pair = doc.createPair(path.at(-1), typeConfig.createNode());
793
- options.onBeforeCreate?.(pair);
794
- if (path.length === 1) doc.add(pair);
795
- else doc.addIn(path.slice(0, -1), pair);
796
- return doc.getIn(path);
797
- }
798
- /**
799
- * Get or create a sequence at the given path
800
- * @param doc - The YAML document
801
- * @param path - The path to the sequence
802
- * @param options - The options for the sequence
803
- */
804
- function getOrCreateSeq(doc, path, options) {
805
- return getOrCreateNode(doc, path, options ?? {}, {
806
- createNode: () => new YAMLSeq(),
807
- typeGuard: isSeq,
808
- typeName: "sequence"
809
- });
810
- }
811
- /**
812
- * Get or create a map at the given path
813
- * @param doc - The YAML document
814
- * @param path - The path to the map
815
- * @param options - The options for the map
816
- */
817
- function getOrCreateMap(doc, path, options) {
818
- return getOrCreateNode(doc, path, options ?? {}, {
819
- createNode: () => new YAMLMap(),
820
- typeGuard: isMap,
821
- typeName: "map"
822
- });
823
- }
824
- /**
825
- * Read the `inputs` map from an existing YAML action definition into a plain
826
- * object. Used by codegen to merge developer-added inputs with the ones the
827
- * generator manages, so regeneration does not strip user-added entries.
828
- *
829
- * @param existingAction - The YAML map representing the existing action.
830
- */
831
- function getExistingInputs(existingAction) {
832
- const inputs = existingAction?.get("inputs");
833
- if (!isMap(inputs)) return {};
834
- return Object.fromEntries(inputs.items.map((item) => {
835
- if (!isPair(item)) return [];
836
- return [String(item.key), item.value];
837
- }));
838
- }
839
- /**
840
- * Read a string field from an existing YAML action definition. Used by codegen
841
- * to preserve developer-set values (e.g. `runtime`) across regeneration
842
- * instead of overwriting them.
843
- *
844
- * @param key - The field to read.
845
- * @param existingAction - The YAML map representing the existing action.
846
- */
847
- function getExistingString(key, existingAction) {
848
- const value = existingAction?.get(key);
849
- return typeof value === "string" ? value : void 0;
850
- }
851
-
852
- //#endregion
853
- //#region ../../packages-private/scripting-utils/source/yaml/codegen.ts
854
- /**
855
- * Create an ext.config.yaml file
856
- * @param path - The path to the ext.config.yaml file
857
- * @param config - The config to build
858
- * @param doc - The document to modify (a new one is created if not provided)
859
- */
860
- async function createOrUpdateExtConfig(path, config, doc) {
861
- const extConfigDoc = doc ?? new Document({});
862
- if (config.web !== void 0) buildWeb(extConfigDoc, config.web);
863
- config.hooks ??= {};
864
- await buildHooks(extConfigDoc, config.hooks);
865
- if (config.operations !== void 0) buildOperations(extConfigDoc, config.operations);
866
- if (config.runtimeManifest !== void 0) buildRuntimeManifest(extConfigDoc, config.runtimeManifest);
867
- await writeExtConfig(path, extConfigDoc);
868
- return extConfigDoc;
869
- }
870
- /**
871
- * Set the top-level `web` scalar only if it's missing.
872
- * Preserves any user-customized value already present.
873
- */
874
- function buildWeb(extConfig, web) {
875
- if (!extConfig.hasIn(["web"])) extConfig.add(extConfig.createPair("web", web));
876
- }
877
- /**
878
- * Build the definition for a runtime action.
879
- *
880
- * Developer-added `inputs` keys (anything not in the generator-managed set) on
881
- * `existingAction` are preserved so that hand-written entries — for example
882
- * factory credentials for `dynamicList` fields — survive regeneration.
883
- *
884
- * A `runtime` already set on `existingAction` is likewise preserved, so a
885
- * developer can pin a different Node runtime (e.g. `nodejs:24`) in
886
- * `ext.config.yaml` once without codegen reverting it on the next run.
887
- *
888
- * @param action - The action definition to build.
889
- * @param existingAction - The action's previous YAML definition, if any.
890
- */
891
- function buildActionDefinition(action, existingAction) {
892
- const actionDef = new YAMLMap();
893
- const existingInputs = getExistingInputs(existingAction);
894
- const existingRuntime = getExistingString("runtime", existingAction);
895
- const managedInputs = { LOG_LEVEL: "$LOG_LEVEL" };
896
- actionDef.set("function", action.function);
897
- actionDef.set("web", action.web ?? "yes");
898
- actionDef.set("runtime", existingRuntime ?? action.runtime ?? "nodejs:24");
899
- actionDef.set("inputs", {
900
- ...existingInputs,
901
- ...managedInputs,
902
- ...action.inputs ?? {}
903
- });
904
- actionDef.set("annotations", { ...action.annotations ?? {
905
- final: true,
906
- "require-adobe-auth": true
907
- } });
908
- const includes = action.include ?? [];
909
- if (includes.length > 0) {
910
- const itemSeq = new YAMLSeq();
911
- itemSeq.flow = true;
912
- for (const [source, target] of includes) itemSeq.items.push(source, target);
913
- const seq = new YAMLSeq();
914
- seq.items.push(itemSeq);
915
- actionDef.set("include", seq);
916
- }
917
- if (action.limits !== void 0) actionDef.set("limits", action.limits);
918
- return actionDef;
919
- }
920
- /**
921
- * Build the `operations` section of the `ext.config.yaml` file
922
- * @param extConfig - The ext.config.yaml file
923
- * @param operations - The operations to build
924
- */
925
- function buildOperations(extConfig, operations) {
926
- const ourOps = operations.workerProcess ?? [];
927
- if (ourOps.length > 0) {
928
- getOrCreateMap(extConfig, ["operations"], { onBeforeCreate: (pair) => {
929
- pair.key.spaceBefore = true;
930
- } });
931
- const workerProcess = getOrCreateSeq(extConfig, ["operations", "workerProcess"], { onBeforeCreate: (pair) => {
932
- pair.key.commentBefore = " These worker processes definitions are auto-generated. Do not remove or manually edit.";
933
- } });
934
- workerProcess.items = [];
935
- workerProcess.items.push(...ourOps.map((op) => {
936
- const map = new YAMLMap();
937
- map.set("type", op.type);
938
- map.set("impl", op.impl);
939
- return map;
940
- }));
941
- } else if (extConfig.hasIn(["operations", "workerProcess"])) extConfig.deleteIn(["operations", "workerProcess"]);
942
- if (operations.view !== void 0) {
943
- getOrCreateMap(extConfig, ["operations"], { onBeforeCreate: (pair) => {
944
- pair.key.spaceBefore = true;
945
- } });
946
- const view = getOrCreateSeq(extConfig, ["operations", "view"]);
947
- if (view.items.length === 0) view.items.push(...operations.view.map((v) => {
948
- const map = new YAMLMap();
949
- map.set("type", v.type);
950
- map.set("impl", v.impl);
951
- return map;
952
- }));
953
- }
954
- const operationsMap = extConfig.getIn(["operations"]);
955
- if (operationsMap instanceof YAMLMap && operationsMap.items.length === 0) extConfig.deleteIn(["operations"]);
956
- }
957
- /**
958
- * Build the `runtimeManifest` section of the `ext.config.yaml` file
959
- * @param extConfig - The ext.config.yaml file
960
- * @param packages - The packages to build
961
- */
962
- function buildRuntimeManifest(extConfig, manifest) {
963
- const packages = manifest.packages ?? {};
964
- if (Object.keys(packages).length === 0) return;
965
- getOrCreateMap(extConfig, ["runtimeManifest"], { onBeforeCreate: (pair) => {
966
- pair.key.spaceBefore = true;
967
- } });
968
- getOrCreateMap(extConfig, ["runtimeManifest", "packages"]);
969
- for (const [name, pkg] of Object.entries(packages)) {
970
- const packageDef = getOrCreateMap(extConfig, [
971
- "runtimeManifest",
972
- "packages",
973
- name
974
- ], { onBeforeCreate: (pair) => {
975
- pair.key.commentBefore = " This package definition is auto-generated. Do not remove or manually edit.";
976
- } });
977
- const existingActions = packageDef.get("actions");
978
- const actions = new YAMLMap();
979
- packageDef.set("license", pkg.license ?? "Apache-2.0");
980
- packageDef.set("actions", actions);
981
- for (const [actionName, action] of Object.entries(pkg.actions ?? {})) {
982
- const existingAction = isMap(existingActions) ? existingActions.get(actionName) : void 0;
983
- const actionDef = buildActionDefinition(action, isMap(existingAction) ? existingAction : void 0);
984
- actions.set(actionName, actionDef);
985
- }
986
- }
987
- }
988
- /**
989
- * Build the `hooks` section of the `ext.config.yaml` file
990
- * @param extConfig - The ext.config.yaml file
991
- * @param hooks - The hooks to build
992
- */
993
- async function buildHooks(extConfig, hooks) {
994
- const generatedHooks = `[${Object.keys(hooks).join(", ")}]`;
995
- const hooksMap = getOrCreateMap(extConfig, ["hooks"], { onBeforeCreate: (pair) => {
996
- pair.key.spaceBefore = true;
997
- pair.key.commentBefore = ` The ${generatedHooks} hooks are auto-generated. Do not remove or manually edit.`;
998
- } });
999
- const projectRoot = await getProjectRootDirectory();
1000
- const packageManager = await detectPackageManager(projectRoot);
1001
- const execCommand = getExecCommand(packageManager);
1002
- for (const [name, command] of Object.entries(hooks)) {
1003
- const fullCommand = `${command.replaceAll("$packageExec", execCommand)}`;
1004
- const prevValue = (hooksMap.get(name) ?? "").trim();
1005
- if (prevValue.endsWith("js") || prevValue.endsWith("ts")) throw new Error(`Conflicting hook definition found. The "${name}" hook needs to be a command, not a script.`);
1006
- hooksMap.set(name, appendCommand(prevValue || void 0, fullCommand));
1007
- }
1008
- }
1009
- /**
1010
- * Write the ext.config.yaml file
1011
- * @param configPath - The path to the ext.config.yaml file
1012
- * @param config - The config to write
1013
- */
1014
- async function writeExtConfig(configPath, doc) {
1015
- const yamlContent = doc.toString({
1016
- defaultStringType: "PLAIN",
1017
- indent: 2,
1018
- lineWidth: 0
1019
- });
1020
- await writeFile(configPath, yamlContent, "utf-8");
1021
- }
1022
-
1023
- //#endregion
1024
- //#region source/commands/generate/actions/lib.ts
1025
- const ESBUILD_PACKAGE = "esbuild@0.28.0";
1026
- const LEADING_DOT_SLASH_PATTERN = /^\.\//u;
1027
- /** Normalize a path for use as an ESM import specifier. */
1028
- function normalizeImportPath(path) {
1029
- const normalizedPath = path.split(sep).join("/");
1030
- return normalizedPath.startsWith(".") ? normalizedPath : `./${normalizedPath}`;
1031
- }
1032
- /** Normalize a generated file path for package.json imports. */
1033
- function normalizePackageJsonPath(path) {
1034
- return normalizeImportPath(path).replace(LEADING_DOT_SLASH_PATTERN, "./");
1035
- }
1036
- /** Add or update the package import alias used by dynamic generated actions. */
1037
- async function updateAppConfigImportAlias(projectRoot, runtimeConfigPath) {
1038
- const pkg = await loadPackageJson(projectRoot);
1039
- if (pkg === null) throw new Error("Could not find package.json.");
1040
- const existingImports = typeof pkg.content.imports === "object" && pkg.content.imports !== null ? pkg.content.imports : {};
1041
- pkg.update({ imports: {
1042
- ...existingImports,
1043
- [APP_CONFIG_IMPORT_ALIAS]: normalizePackageJsonPath(runtimeConfigPath)
1044
- } });
1045
- await pkg.save();
1046
- }
1047
- /** Write an ESM passthrough module that re-exports an app config file. */
1048
- async function writeJavaScriptAppConfigModule(configFilePath, outputPath) {
1049
- const configImportPath = normalizeImportPath(relative(dirname(outputPath), configFilePath));
1050
- const isJson = extname(configFilePath) === ".json";
1051
- const importAttributes = isJson ? " with { type: \"json\" }" : "";
1052
- const defaultExport = isJson ? `export default /** @type {import("@adobe/aio-commerce-lib-app/config").CommerceAppConfig} */ (appConfig);` : "export default appConfig;";
1053
- await writeFile(outputPath, [
1054
- "// This file has been auto-generated by `@adobe/aio-commerce-lib-app`",
1055
- "// Do not modify this file directly",
1056
- "",
1057
- `import appConfig from "${configImportPath}"${importAttributes};`,
1058
- "",
1059
- isJson ? null : `export * from "${configImportPath}";`,
1060
- defaultExport,
1061
- ""
1062
- ].filter((item) => item !== null).join("\n"), "utf-8");
1063
- }
1064
- /**
1065
- * Bundles a TypeScript app config file into runtime-safe ESM.
1066
- * @param configFilePath - Source TypeScript app config path.
1067
- * @param outputPath - Generated ESM module path.
1068
- * @param projectRoot - Resolved project root used to run the bundler.
1069
- */
1070
- async function bundleTypeScriptAppConfigModule(configFilePath, outputPath, projectRoot) {
1071
- const packageManager = await detectPackageManager(projectRoot);
1072
- const { command, args } = getPackageExecutionCommand(packageManager, [
1073
- ESBUILD_PACKAGE,
1074
- configFilePath,
1075
- "--bundle",
1076
- "--format=esm",
1077
- "--log-level=silent",
1078
- `--outfile=${outputPath}`,
1079
- "--packages=external",
1080
- "--platform=node",
1081
- "--target=node22"
1082
- ], { allowBuild: "esbuild" });
1083
- const result = spawnSync(command, args, {
1084
- cwd: projectRoot,
1085
- stdio: "inherit"
1086
- });
1087
- if (result.error || result.status !== 0) throw new Error(`Could not bundle the TypeScript app config with ${ESBUILD_PACKAGE}.`, { cause: result.error ?? /* @__PURE__ */ new Error(`Bundle command exited with code ${result.status}`) });
1088
- }
1089
- /**
1090
- * Write an ESM module that re-exports the static app manifest JSON so generated
1091
- * actions can import it via the alias without needing a JSON import attribute.
1092
- * @param projectRoot - Resolved project root where the module is generated.
1093
- */
1094
- async function prepareStaticAppConfigImportAlias(projectRoot) {
1095
- const runtimeConfigPath = getRuntimeAppConfigPath();
1096
- const outputPath = join(projectRoot, runtimeConfigPath);
1097
- await makeOutputDirFor(dirname(runtimeConfigPath), projectRoot);
1098
- await writeJavaScriptAppConfigModule(join(projectRoot, getManifestPath()), outputPath);
1099
- }
1100
- /**
1101
- * Prepare the runtime config module consumed by generated actions and its
1102
- * `#app.commerce.config` package import alias.
1103
- *
1104
- * TypeScript configs are bundled into a generated runtime module. JavaScript
1105
- * configs use a passthrough module, while serializable default-only configs use
1106
- * a re-export of the validated JSON manifest.
1107
- * @param appManifest - App configuration used to select the module format.
1108
- * @param projectRoot - Resolved project root where the module is generated.
1109
- */
1110
- async function prepareRuntimeAppConfigModule(appManifest, projectRoot) {
1111
- const configFilePath = await resolveCommerceAppConfig(projectRoot);
1112
- if (await requiresJavaScriptAppConfig(appManifest, projectRoot)) {
1113
- if (configFilePath === null) throw new Error("Generating a runtime config module requires an app.commerce.config.* file.");
1114
- await makeOutputDirFor(dirname(getRuntimeAppConfigPath()), projectRoot);
1115
- const outputPath = join(projectRoot, getRuntimeAppConfigPath());
1116
- if (isTypeScriptConfig(configFilePath)) await bundleTypeScriptAppConfigModule(configFilePath, outputPath, projectRoot);
1117
- else await writeJavaScriptAppConfigModule(configFilePath, outputPath);
1118
- } else await prepareStaticAppConfigImportAlias(projectRoot);
1119
- await updateAppConfigImportAlias(projectRoot, getRuntimeAppConfigPath());
1120
- }
1121
- /**
1122
- * Reads an extension point's ext.config.yaml document and path.
1123
- * @param extensionPointId - The extension point ID to read the config for.
1124
- * @param projectRoot - Resolved project root containing the extension config.
1125
- */
1126
- async function readExtConfig(extensionPointId, projectRoot) {
1127
- const extConfigPath = join(projectRoot, getExtConfigPath(extensionPointId));
1128
- try {
1129
- return {
1130
- doc: await readYamlFile(extConfigPath),
1131
- path: extConfigPath
1132
- };
1133
- } catch (error) {
1134
- const reason = error instanceof Error ? error.message : "Unexpected error while reading the file.";
1135
- throw new Error([
1136
- `Could not read ext.config.yaml for ${extensionPointId} at ${extConfigPath}.`,
1137
- `Make sure the file exists and is valid YAML. You can regenerate it with "aio-commerce-lib-app generate actions".`,
1138
- `Reason: ${reason}`
1139
- ].join("\n"), { cause: error });
1140
- }
1141
- }
1142
- /**
1143
- * Updates an extension point's ext.config.yaml with generated configuration.
1144
- * @param appConfig - App configuration used to build the extension config.
1145
- * @param extensionPointId - Extension point whose config is updated.
1146
- * @param projectRoot - Resolved project root containing the extension config.
1147
- */
1148
- async function updateExtConfig(appConfig, extensionPointId, projectRoot) {
1149
- consola.info(`Updating ext.config.yaml for ${extensionPointId}...`);
1150
- await makeOutputDirFor(getExtensionPointFolderPath(extensionPointId), projectRoot);
1151
- const { path: extConfigPath, doc: extConfigDoc } = await readExtConfig(extensionPointId, projectRoot);
1152
- let extConfig;
1153
- switch (extensionPointId) {
1154
- case EXTENSIBILITY_EXTENSION_POINT_ID:
1155
- extConfig = buildAppManagementExtConfig(appConfig);
1156
- break;
1157
- case CONFIGURATION_EXTENSION_POINT_ID:
1158
- extConfig = buildBusinessConfigurationExtConfig();
1159
- break;
1160
- case BACKEND_UI_V2_EXTENSION_POINT_ID:
1161
- extConfig = buildAdminUiV2ExtConfig(appConfig);
1162
- break;
1163
- default: throw new Error(`Unsupported extension point: ${extensionPointId}`);
1164
- }
1165
- await createOrUpdateExtConfig(extConfigPath, extConfig, extConfigDoc);
1166
- return extConfig;
1167
- }
1168
- /**
1169
- * Generates runtime action files from templates.
1170
- * @param appManifest - App configuration used to populate action templates.
1171
- * @param actions - Runtime actions to generate.
1172
- * @param extensionPointId - Extension point receiving the generated actions.
1173
- * @param templatesDir - Directory containing action templates.
1174
- * @param projectRoot - Resolved project root where actions are generated.
1175
- */
1176
- async function generateActionFiles(appManifest, actions, extensionPointId, templatesDir, projectRoot) {
1177
- consola.start("Generating runtime actions...");
1178
- await makeOutputDirFor(getActionsDir(extensionPointId), projectRoot);
1179
- const outputFiles = await Promise.all(actions.map(async (action) => {
1180
- const templatePath = join(templatesDir, action.templateFile);
1181
- let template = await readFile(templatePath, "utf-8");
1182
- if (action.name === "installation") {
1183
- const customScriptsTemplatePath = join(templatesDir, "app-management", "custom-scripts.js.template");
1184
- const scriptsTemplate = await generateCustomScriptsTemplate(await readFile(customScriptsTemplatePath, "utf-8"), appManifest, projectRoot);
1185
- template = applyCustomScripts(template, scriptsTemplate);
1186
- }
1187
- const actionPath = join(projectRoot, getActionPath(extensionPointId, action.name));
1188
- await writeFile(actionPath, template, "utf-8");
1189
- return ` ${relative(projectRoot, actionPath)}`;
1190
- }));
1191
- consola.success(`Generated ${actions.length} action(s)`);
1192
- consola.log.raw(formatTree(outputFiles));
1193
- }
1194
- /**
1195
- * Applies the given custom scripts template code to the given installation template.
1196
- * @param installationTemplate - The installation code runtime action template
1197
- * @param customScriptsTemplate - The custom scripts dynamically generated template.
1198
- */
1199
- function applyCustomScripts(installationTemplate, customScriptsTemplate) {
1200
- if (customScriptsTemplate !== null) return installationTemplate.replace(CUSTOM_SCRIPTS_LOADER_PLACEHOLDER, customScriptsTemplate).replace("const args = { appConfig };", "const args = { appConfig, customScriptsLoader };");
1201
- consola.debug("No custom installation steps found, skipping custom-scripts.js generation...");
1202
- return installationTemplate.replace(CUSTOM_SCRIPTS_LOADER_PLACEHOLDER, "// No custom installation scripts configured");
1203
- }
1204
- /**
1205
- * Generates installation code that imports configured custom scripts.
1206
- * @param template - Custom scripts template content.
1207
- * @param appManifest - App configuration containing custom installation steps.
1208
- * @param projectRoot - Resolved project root used to resolve script paths.
1209
- */
1210
- async function generateCustomScriptsTemplate(template, appManifest, projectRoot) {
1211
- if (!hasCustomInstallationSteps(appManifest)) return null;
1212
- const installationActionDir = join(projectRoot, getActionsDir(EXTENSIBILITY_EXTENSION_POINT_ID));
1213
- const customSteps = appManifest.installation.customInstallationSteps;
1214
- const importStatements = customSteps.map((step, index) => {
1215
- const absoluteScriptPath = join(projectRoot, step.script);
1216
- let relativeImportPath = relative(installationActionDir, absoluteScriptPath);
1217
- if (!relativeImportPath.startsWith(".")) relativeImportPath = `./${relativeImportPath}`;
1218
- relativeImportPath = relativeImportPath.replace(/\\/g, "/");
1219
- return `import * as ${`customScript${index}`} from "${relativeImportPath}";`;
1220
- }).join("\n");
1221
- const scriptMap = customSteps.map((step, index) => {
1222
- const entry = `"${step.script}": ${`customScript${index}`},`;
1223
- return entry.padStart(entry.length + 6);
1224
- }).join("\n");
1225
- return template.replace(CUSTOM_IMPORTS_PLACEHOLDER, importStatements).replace(CUSTOM_SCRIPTS_MAP_PLACEHOLDER, scriptMap);
1226
- }
1227
-
1228
- //#endregion
1229
29
  //#region source/commands/generate/actions/main.ts
1230
30
  /**
1231
31
  * Generates extension configs, runtime actions, and Admin UI web source for the configured domains.
@@ -1233,7 +33,7 @@ async function generateCustomScriptsTemplate(template, appManifest, projectRoot)
1233
33
  * @param projectRoot - Resolved project root where files are generated.
1234
34
  * @param templatesDir - Directory containing generation templates.
1235
35
  */
1236
- async function run$8(appManifest, projectRoot, templatesDir = TEMPLATES_DIR) {
36
+ async function run$2(appManifest, projectRoot, templatesDir = TEMPLATES_DIR) {
1237
37
  await prepareRuntimeAppConfigModule(appManifest, projectRoot);
1238
38
  const appManagementExtConfig = await updateExtConfig(appManifest, EXTENSIBILITY_EXTENSION_POINT_ID, projectRoot);
1239
39
  await generateActionFiles(appManifest, getRuntimeActions(appManagementExtConfig, "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID, templatesDir, projectRoot);
@@ -1250,266 +50,10 @@ async function run$8(appManifest, projectRoot, templatesDir = TEMPLATES_DIR) {
1250
50
  }
1251
51
  }
1252
52
  /** Run the generate actions command */
1253
- async function exec$8() {
1254
- try {
1255
- const projectRoot = await getProjectRootDirectory();
1256
- await run$8(await loadAppManifest(projectRoot), projectRoot);
1257
- } catch (error) {
1258
- if (error instanceof CommerceSdkValidationError) consola.error(error.display());
1259
- else consola.error(error);
1260
- process.exit(1);
1261
- }
1262
- }
1263
-
1264
- //#endregion
1265
- //#region source/commands/generate/manifest/main.ts
1266
- /**
1267
- * Generates or removes the static app manifest as required by the app config.
1268
- * @param appConfig - Validated app configuration to serialize.
1269
- * @param projectRoot - Resolved project root where the manifest is generated.
1270
- */
1271
- async function run$7(appConfig, projectRoot) {
1272
- if (await requiresJavaScriptAppConfig(appConfig, projectRoot)) {
1273
- const stalePath = join(projectRoot, getManifestPath());
1274
- if (await access(stalePath).then(() => true, () => false)) {
1275
- await rm(stalePath, { force: true });
1276
- consola.success(`Removed stale ${APP_MANIFEST_FILE}`);
1277
- }
1278
- return;
1279
- }
1280
- consola.info("Generating app manifest...");
1281
- const contents = stringify$1(appConfig, null, 2);
1282
- const outputDir = await makeOutputDirFor(getGeneratedDir(EXTENSIBILITY_EXTENSION_POINT_ID), projectRoot);
1283
- const manifestPath = join(outputDir, APP_MANIFEST_FILE);
1284
- await writeFile(manifestPath, contents, "utf-8");
1285
- consola.success(`Generated ${APP_MANIFEST_FILE}\n`);
1286
- }
1287
- /** Run the generate manifest command */
1288
- async function exec$7() {
1289
- try {
1290
- const projectRoot = await getProjectRootDirectory();
1291
- await run$7(await loadAppManifest(projectRoot), projectRoot);
1292
- } catch (error) {
1293
- if (error instanceof CommerceSdkValidationError) consola.error(error.display());
1294
- else consola.error(error);
1295
- process.exit(1);
1296
- }
1297
- }
1298
-
1299
- //#endregion
1300
- //#region ../../packages-private/scripting-utils/source/filesystem/helpers.ts
1301
- /**
1302
- * Touch a file, creating it if it doesn't exist.
1303
- * @param filePath The path of the file to touch
1304
- */
1305
- async function touch(filePath) {
1306
- await writeFile(filePath, "", { flag: "wx" }).catch((e) => {
1307
- if (e.code !== "EEXIST") throw e;
1308
- });
1309
- }
1310
-
1311
- //#endregion
1312
- //#region source/commands/generate/schema/main.ts
1313
- /**
1314
- * Generates the business configuration schema and prepares encryption when needed.
1315
- * @param appConfig - Validated app configuration containing the schema.
1316
- * @param projectRoot - Resolved project root where schema files are managed.
1317
- */
1318
- async function run$6(appConfig, projectRoot) {
1319
- if (!hasBusinessConfigSchema(appConfig)) {
1320
- consola.debug("Business configuration schema not found in application configuration. Nothing to do.");
1321
- return;
1322
- }
1323
- const envPath = join(projectRoot, ".env");
1324
- await touch(envPath);
1325
- process.loadEnvFile(envPath);
1326
- if (appConfig.businessConfig.schema.some((field) => field.type === "password")) {
1327
- const packageExec = getExecCommand(await detectPackageManager(projectRoot));
1328
- if ("AIO_COMMERCE_CONFIG_ENCRYPTION_KEY" in process.env && String(process.env.AIO_COMMERCE_CONFIG_ENCRYPTION_KEY).trim().length > 0) execSync(`${packageExec} aio-commerce-lib-config encryption validate`);
1329
- else execSync(`${packageExec} aio-commerce-lib-config encryption setup`);
1330
- }
1331
- if (hasDynamicAppConfig(appConfig)) {
1332
- const stalePath = join(projectRoot, getSchemaPath());
1333
- if (await access(stalePath).then(() => true, () => false)) {
1334
- await rm(stalePath, { force: true });
1335
- consola.success(`Removed stale ${CONFIG_SCHEMA_FILE_NAME}`);
1336
- }
1337
- return;
1338
- }
1339
- consola.info("Generating configuration schema...");
1340
- const outputDir = await makeOutputDirFor(getGeneratedDir(CONFIGURATION_EXTENSION_POINT_ID), projectRoot);
1341
- const contents = stringify$1(appConfig.businessConfig.schema, null, 2);
1342
- const schemaPath = join(outputDir, CONFIG_SCHEMA_FILE_NAME);
1343
- await writeFile(schemaPath, contents, "utf-8");
1344
- consola.success(`Generated ${CONFIG_SCHEMA_FILE_NAME}\n`);
1345
- }
1346
- /** Run the generate schema command */
1347
- async function exec$6() {
53
+ async function exec$2() {
1348
54
  try {
1349
55
  const projectRoot = await getProjectRootDirectory();
1350
- await run$6(await loadAppManifest(projectRoot), projectRoot);
1351
- } catch (error) {
1352
- if (error instanceof CommerceSdkValidationError) consola.error(error.display());
1353
- else consola.error(error);
1354
- process.exit(1);
1355
- }
1356
- }
1357
-
1358
- //#endregion
1359
- //#region ../../packages-private/scripting-utils/source/aio/auth.ts
1360
- const { context: context$1, getToken } = aioIms;
1361
- /** Gets an IMS access token for the current CLI IMS context. */
1362
- async function getUserToken() {
1363
- const contextName = await context$1.getCurrent() ?? "cli";
1364
- return getToken(contextName, {});
1365
- }
1366
-
1367
- //#endregion
1368
- //#region ../../packages-private/scripting-utils/source/aio/context.ts
1369
- /**
1370
- * Reads the current App Builder project and Runtime namespace from the local AIO CLI config.
1371
- * @throws If either the project or the runtime namespace is not configured.
1372
- */
1373
- function getAioProjectContext() {
1374
- const project = config.get("project");
1375
- const namespace = config.get("runtime.namespace");
1376
- if (!(project && namespace)) throw new Error("The current App Builder project and Runtime namespace are required");
1377
- return {
1378
- namespace,
1379
- project
1380
- };
1381
- }
1382
- /** Reads the configured AIO CLI environment, defaulting to `"prod"`. */
1383
- function getAioCliEnv() {
1384
- return config.get("cli.env") === "stage" ? "stage" : "prod";
1385
- }
1386
-
1387
- //#endregion
1388
- //#region source/commands/hooks/post-app-deploy/polling.ts
1389
- const POLL_INTERVAL_MS = 1e3;
1390
- const UpgradeStateSchema = v.object({
1391
- failure: v.optional(v.object({
1392
- key: v.string(),
1393
- message: v.optional(v.string())
1394
- })),
1395
- id: v.string(),
1396
- status: v.picklist([
1397
- "pending",
1398
- "in-progress",
1399
- "succeeded",
1400
- "failed"
1401
- ])
1402
- });
1403
- /** Reads the latest upgrade state from the installation action. */
1404
- async function getUpgradeState(request) {
1405
- try {
1406
- const response = await ky.get(request.endpoint, { headers: request.headers });
1407
- if (response.status === 204) return null;
1408
- const parsed = v.safeParse(UpgradeStateSchema, await response.json());
1409
- return parsed.success ? parsed.output : null;
1410
- } catch (error) {
1411
- consola.debug("Unable to read upgrade status; stopping upgrade polling.", { error });
1412
- return null;
1413
- }
1414
- }
1415
- /** Waits for an automatic upgrade when its status remains available. */
1416
- async function waitForAutomaticUpgrade(request) {
1417
- const state = await getUpgradeState(request);
1418
- if (!state) return;
1419
- if (state.status === "succeeded") {
1420
- consola.success("App upgrade completed.\n");
1421
- return;
1422
- }
1423
- if (state.status === "failed") {
1424
- const reason = state.failure?.message ?? state.failure?.key ?? "The lifecycle attempt failed";
1425
- throw new Error(`App upgrade failed: ${reason}`);
1426
- }
1427
- consola.start("App upgrade is in progress...");
1428
- await setTimeout(POLL_INTERVAL_MS);
1429
- return waitForAutomaticUpgrade(request);
1430
- }
1431
-
1432
- //#endregion
1433
- //#region source/commands/hooks/post-app-deploy/main.ts
1434
- /** Returns true for a no-op reason defined by the upgrade API contract. */
1435
- function isSkippedReason(reason) {
1436
- return reason === "already-current" || reason === "not-associated" || reason === "not-installed";
1437
- }
1438
- /** Returns true if the result indicates that the upgrade was skipped. */
1439
- function isSkippedResult(result) {
1440
- return "skipped" in result;
1441
- }
1442
- /** Invokes the upgrade action. */
1443
- async function createUpgradeRequest() {
1444
- const { project, namespace } = getAioProjectContext();
1445
- const token = await getUserToken();
1446
- const endpoint = `https://${namespace}.adobeioruntime.net/api/v1/web/app-management/installation`;
1447
- const ioEventsEnv = getAioCliEnv();
1448
- const ioEventsUrl = ioEventsEnv === "stage" ? "https://events-stage.adobe.io" : "https://events.adobe.io";
1449
- return {
1450
- body: {
1451
- appData: {
1452
- consumerOrgId: project.org.id,
1453
- orgName: project.org.name,
1454
- projectId: project.id,
1455
- projectName: project.name,
1456
- projectTitle: project.title,
1457
- workspaceId: project.workspace.id,
1458
- workspaceName: project.workspace.name,
1459
- workspaceTitle: project.workspace.title
1460
- },
1461
- ioEventsEnv,
1462
- ioEventsUrl
1463
- },
1464
- endpoint,
1465
- headers: {
1466
- Authorization: `Bearer ${token}`,
1467
- [INSTALLATION_INVOCATION_SOURCE_HEADER]: POST_APP_DEPLOY_INVOCATION_SOURCE,
1468
- "x-gw-ims-org-id": project.org.ims_org_id
1469
- }
1470
- };
1471
- }
1472
- /** Invokes the upgrade action. */
1473
- async function invokeAction(request) {
1474
- consola.debug(`Upgrade endpoint: ${request.endpoint}`);
1475
- try {
1476
- return await ky.post(request.endpoint, {
1477
- headers: request.headers,
1478
- json: request.body
1479
- }).json();
1480
- } catch (error) {
1481
- if (error instanceof HTTPError) {
1482
- const details = await error.response.json();
1483
- if (error.response.status === 409 && isSkippedReason(details.reason)) return {
1484
- reason: details.reason,
1485
- skipped: true
1486
- };
1487
- throw new Error(`Failed to trigger app upgrade (HTTP ${error.response.status}): ${JSON.stringify(details, null, 2)}`, { cause: error });
1488
- }
1489
- throw error;
1490
- }
1491
- }
1492
- /** Invokes the deployed app's upgrade endpoint. */
1493
- async function run$5() {
1494
- const { upgradeMode } = (await parseCommerceAppConfig()).metadata;
1495
- consola.log("");
1496
- consola.start("Checking for app upgrades...");
1497
- const request = await createUpgradeRequest();
1498
- const result = await invokeAction(request);
1499
- if (isSkippedResult(result)) {
1500
- consola.info(`No upgrade was run: ${result.reason}.\n`);
1501
- return result;
1502
- }
1503
- 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.`);
1504
- else consola.success(`You have set ${colors.cyan("metadata.upgradeMode")} to ${colors.cyan("auto")}. The upgrade plan has been created. Execution will begin shortly.`);
1505
- consola.box(["Upgrade plan", JSON.stringify(result.plan, null, 2)].join("\n\n"));
1506
- if (upgradeMode === "auto") await waitForAutomaticUpgrade(request);
1507
- return result;
1508
- }
1509
- /** Runs the post-app-deploy hook. */
1510
- async function exec$5() {
1511
- try {
1512
- await run$5();
56
+ await run$2(await loadAppManifest(projectRoot), projectRoot);
1513
57
  } catch (error) {
1514
58
  if (error instanceof CommerceSdkValidationError) consola.error(error.display());
1515
59
  else consola.error(error);
@@ -1525,192 +69,17 @@ async function exec$5() {
1525
69
  * @param projectRoot - Resolved project root where artifacts are generated.
1526
70
  * @param templatesDir - Optional directory containing generation templates.
1527
71
  */
1528
- async function run$4(appManifest, projectRoot, templatesDir) {
1529
- await run$8(appManifest, projectRoot, templatesDir);
1530
- await run$7(appManifest, projectRoot);
1531
- await run$6(appManifest, projectRoot);
72
+ async function run$1(appManifest, projectRoot, templatesDir) {
73
+ await run$2(appManifest, projectRoot, templatesDir);
74
+ await run$3(appManifest, projectRoot);
75
+ await run$4(appManifest, projectRoot);
1532
76
  }
1533
77
  /** Runs the postinstall hook */
1534
- async function exec$4() {
78
+ async function exec$1() {
1535
79
  consola$1.debug("Running lib-app postinstall hook");
1536
80
  try {
1537
81
  const projectRoot = await getProjectRootDirectory();
1538
- await run$4(await loadAppManifest(projectRoot), projectRoot);
1539
- } catch (error) {
1540
- if (error instanceof CommerceSdkValidationError) consola$1.error(error.display());
1541
- else consola$1.error(error);
1542
- process.exit(1);
1543
- }
1544
- }
1545
-
1546
- //#endregion
1547
- //#region ../../packages-private/scripting-utils/source/env.ts
1548
- /**
1549
- * This module exports shared environment utilities for the AIO Commerce SDK.
1550
- * @packageDocumentation
1551
- */
1552
- const { context } = aioIms;
1553
- const IMS_KEYS = {
1554
- client_id: "AIO_COMMERCE_AUTH_IMS_CLIENT_ID",
1555
- client_secrets: "AIO_COMMERCE_AUTH_IMS_CLIENT_SECRETS",
1556
- ims_org_id: "AIO_COMMERCE_AUTH_IMS_ORG_ID",
1557
- scopes: "AIO_COMMERCE_AUTH_IMS_SCOPES",
1558
- technical_account_email: "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_EMAIL",
1559
- technical_account_id: "AIO_COMMERCE_AUTH_IMS_TECHNICAL_ACCOUNT_ID"
1560
- };
1561
- /**
1562
- * Replaces or creates an environment variable in a .env file
1563
- * @param filePath - The path to the .env file
1564
- * @param key - The environment variable key to replace or create
1565
- * @param value - The new value for the environment variable
1566
- */
1567
- function replaceEnvVar(filePath, key, value) {
1568
- const envPath = path.resolve(filePath);
1569
- const envLines = readFileSync(envPath, "utf8").split("\n");
1570
- const formattedValue = value.includes(" ") ? `"${value}"` : value;
1571
- let keyExists = false;
1572
- const updatedLines = envLines.map((line) => {
1573
- if (line.trim().startsWith("#") || !line.includes("=")) return line;
1574
- const [currentKey] = line.split("=");
1575
- if (currentKey === key) {
1576
- keyExists = true;
1577
- return `${key}=${formattedValue}`;
1578
- }
1579
- return line;
1580
- });
1581
- if (!keyExists) updatedLines.push(`${key}=${formattedValue}`);
1582
- writeFileSync(envPath, updatedLines.join("\n"), "utf8");
1583
- }
1584
- /**
1585
- * Sets the `NODE_ENV` environment variable in the app `.env` file, so the web
1586
- * bundler (Parcel) ships the matching React build. Creates the `.env` if absent.
1587
- * @param mode - The environment mode to write into `NODE_ENV`.
1588
- * @param projectRoot - Resolved project root containing the `.env` file.
1589
- */
1590
- function setNodeEnv(mode, projectRoot) {
1591
- const envPath = path.join(projectRoot, ".env");
1592
- if (!existsSync(envPath)) writeFileSync(envPath, "", "utf8");
1593
- replaceEnvVar(envPath, "NODE_ENV", mode);
1594
- }
1595
- /** Resolves the IMS server to server context from the project workspace credentials. */
1596
- function resolveImsS2SContext() {
1597
- const [credential] = (config.get("project.workspace.details.credentials") ?? []).filter(({ integration_type }) => integration_type === "oauth_server_to_server").map(({ name }) => name);
1598
- if (!credential) return Promise.resolve(null);
1599
- return context.get(credential);
1600
- }
1601
- /**
1602
- * Syncs the IMS credentials environment variables from the configured IMS context in
1603
- * the .env file, in a way that is compatible with `@adobe/aio-commerce-lib-auth`.
1604
- * @param projectRoot - Resolved project root containing the `.env` file.
1605
- */
1606
- async function syncImsCredentials(projectRoot) {
1607
- const envPath = path.join(projectRoot, ".env");
1608
- if (!existsSync(envPath)) return {
1609
- ok: false,
1610
- reason: "missing-env"
1611
- };
1612
- const envVars = dotenv.parse(readFileSync(envPath, "utf8"));
1613
- const imsContext = await resolveImsS2SContext();
1614
- if (!imsContext) return {
1615
- ok: false,
1616
- reason: "no-ims-context"
1617
- };
1618
- const { data } = imsContext;
1619
- for (const [key, value] of Object.entries(data)) {
1620
- const oauthKey = IMS_KEYS[key];
1621
- if (!oauthKey) continue;
1622
- if (!envVars[oauthKey] || envVars[oauthKey] !== value) replaceEnvVar(envPath, oauthKey, value);
1623
- }
1624
- return { ok: true };
1625
- }
1626
-
1627
- //#endregion
1628
- //#region source/commands/hooks/pre-app-build.ts
1629
- /**
1630
- * Runs the pre-app-build hook for the given extension.
1631
- * @param extension - The extension to run the hook for.
1632
- * @param projectRoot - Resolved project root containing extension files.
1633
- * @param templatesDir - Directory containing action templates.
1634
- */
1635
- async function run$3(extension, projectRoot, templatesDir = TEMPLATES_DIR) {
1636
- const appManifest = await loadAppManifest(projectRoot);
1637
- await prepareRuntimeAppConfigModule(appManifest, projectRoot);
1638
- if (extension === "extensibility/1") {
1639
- const { doc: extensibilityExtConfig } = await readExtConfig(EXTENSIBILITY_EXTENSION_POINT_ID, projectRoot);
1640
- await run$7(appManifest, projectRoot);
1641
- await generateActionFiles(appManifest, getRuntimeActions(extensibilityExtConfig.toJS(), "app-management"), EXTENSIBILITY_EXTENSION_POINT_ID, templatesDir, projectRoot);
1642
- consola$1.info("Syncing IMS credentials...");
1643
- await syncImsCredentials(projectRoot);
1644
- return;
1645
- }
1646
- if (extension === "configuration/1") {
1647
- const { doc: businessConfigExtConfig } = await readExtConfig(CONFIGURATION_EXTENSION_POINT_ID, projectRoot);
1648
- await run$6(appManifest, projectRoot);
1649
- await generateActionFiles(appManifest, getRuntimeActions(businessConfigExtConfig.toJS(), "business-configuration"), CONFIGURATION_EXTENSION_POINT_ID, templatesDir, projectRoot);
1650
- return;
1651
- }
1652
- if (extension === "backend-ui/2") {
1653
- if (hasBackendUiV2Components(appManifest)) {
1654
- const extConfig = await updateExtConfig(appManifest, BACKEND_UI_V2_EXTENSION_POINT_ID, projectRoot);
1655
- if (extConfig.operations?.view) {
1656
- await prepareWebSourceImportAlias(extConfig, projectRoot);
1657
- await generateWebSrc(extConfig, appManifest.metadata.displayName, projectRoot, templatesDir);
1658
- await setNodeEnv("production", projectRoot);
1659
- }
1660
- }
1661
- return;
1662
- }
1663
- throw new Error(`Unsupported extension: ${extension}`);
1664
- }
1665
- /** Runs the pre-app-build hook */
1666
- async function exec$3() {
1667
- consola$1.debug("Running lib-app pre-app-build hook");
1668
- const rawExtension = process.env.EXTENSION;
1669
- try {
1670
- if (!rawExtension) throw new Error("EXTENSION environment variable is not set");
1671
- await run$3(rawExtension, await getProjectRootDirectory());
1672
- } catch (error) {
1673
- if (error instanceof CommerceSdkValidationError) consola$1.error(error.display());
1674
- else consola$1.error(error);
1675
- process.exit(1);
1676
- }
1677
- }
1678
-
1679
- //#endregion
1680
- //#region source/commands/hooks/pre-app-dev.ts
1681
- /**
1682
- * Resets the web build back to development by writing `NODE_ENV` to the project `.env`.
1683
- * @param projectRoot - Resolved project root containing the `.env` file.
1684
- */
1685
- async function run$2(projectRoot) {
1686
- await setNodeEnv("development", projectRoot);
1687
- }
1688
- /** Runs the pre-app-dev hook. */
1689
- async function exec$2() {
1690
- consola$1.debug("Running lib-app pre-app-dev hook");
1691
- try {
1692
- await run$2(await getProjectRootDirectory());
1693
- } catch (error) {
1694
- if (error instanceof CommerceSdkValidationError) consola$1.error(error.display());
1695
- else consola$1.error(error);
1696
- process.exit(1);
1697
- }
1698
- }
1699
-
1700
- //#endregion
1701
- //#region source/commands/hooks/pre-app-run.ts
1702
- /**
1703
- * Resets the web build back to development by writing `NODE_ENV` to the project `.env`.
1704
- * @param projectRoot - Resolved project root containing the `.env` file.
1705
- */
1706
- async function run$1(projectRoot) {
1707
- await setNodeEnv("development", projectRoot);
1708
- }
1709
- /** Runs the pre-app-run hook. */
1710
- async function exec$1() {
1711
- consola$1.debug("Running lib-app pre-app-run hook");
1712
- try {
1713
- await run$1(await getProjectRootDirectory());
82
+ await run$1(await loadAppManifest(projectRoot), projectRoot);
1714
83
  } catch (error) {
1715
84
  if (error instanceof CommerceSdkValidationError) consola$1.error(error.display());
1716
85
  else consola$1.error(error);
@@ -2101,9 +470,9 @@ function installDependencies(packageManager, domains, projectRoot) {
2101
470
  */
2102
471
  async function runGeneration(appConfig, execCommand, projectRoot) {
2103
472
  try {
2104
- await run$8(appConfig, projectRoot);
2105
- await run$7(appConfig, projectRoot);
2106
- await run$6(appConfig, projectRoot);
473
+ await run$2(appConfig, projectRoot);
474
+ await run$3(appConfig, projectRoot);
475
+ await run$4(appConfig, projectRoot);
2107
476
  } catch (error) {
2108
477
  throw new Error(`Failed to run generation command. Please run manually: ${execCommand} aio-commerce-lib-app generate all`, { cause: error });
2109
478
  }
@@ -2122,7 +491,7 @@ async function ensureInstallYaml(domains, config, projectRoot) {
2122
491
 
2123
492
  //#endregion
2124
493
  //#region source/commands/init/main.ts
2125
- const REQUIRED_DEPENDENCIES = ["@adobe/aio-commerce-sdk", `@adobe/aio-commerce-lib-app@2.0.0`];
494
+ const REQUIRED_DEPENDENCIES = ["@adobe/aio-commerce-sdk", `@adobe/aio-commerce-lib-app@2.0.1-alpha-20260923160203`];
2126
495
  /**
2127
496
  * Initializes a Commerce App project.
2128
497
  * @param flags - Non-interactive initialization answers.
@@ -2195,25 +564,19 @@ Examples:
2195
564
  `;
2196
565
  /** Run all generate targets in sequence */
2197
566
  async function generateAll() {
2198
- await exec$8();
2199
- await exec$7();
2200
- await exec$6();
567
+ await exec$2();
568
+ await exec$3();
569
+ await exec$4();
2201
570
  }
2202
571
  /** Command handlers registry mapping command names to their subcommand handlers */
2203
572
  const COMMANDS = {
2204
573
  generate: {
2205
- actions: exec$8,
574
+ actions: exec$2,
2206
575
  all: generateAll,
2207
- manifest: exec$7,
2208
- schema: exec$6
2209
- },
2210
- hooks: {
2211
- "post-app-deploy": exec$5,
2212
- postinstall: exec$4,
2213
- "pre-app-build": exec$3,
2214
- "pre-app-dev": exec$2,
2215
- "pre-app-run": exec$1
576
+ manifest: exec$3,
577
+ schema: exec$4
2216
578
  },
579
+ hooks: { postinstall: exec$1 },
2217
580
  init: exec
2218
581
  };
2219
582
  /**