@salesforce/storefront-next-dev 0.2.0-alpha.2 → 0.3.0-alpha.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/dist/cartridge-services/index.d.ts.map +1 -1
  2. package/dist/cartridge-services/index.js +171 -50
  3. package/dist/cartridge-services/index.js.map +1 -1
  4. package/dist/commands/create-bundle.js +12 -11
  5. package/dist/commands/create-instructions.js +7 -5
  6. package/dist/commands/create-storefront.js +18 -22
  7. package/dist/commands/deploy-cartridge.js +67 -26
  8. package/dist/commands/dev.js +6 -4
  9. package/dist/commands/extensions/create.js +2 -0
  10. package/dist/commands/extensions/install.js +3 -7
  11. package/dist/commands/extensions/list.js +2 -0
  12. package/dist/commands/extensions/remove.js +3 -7
  13. package/dist/commands/generate-cartridge.js +23 -2
  14. package/dist/commands/preview.js +15 -10
  15. package/dist/commands/push.js +25 -19
  16. package/dist/commands/validate-cartridge.js +51 -0
  17. package/dist/config.js +74 -47
  18. package/dist/configs/react-router.config.d.ts.map +1 -1
  19. package/dist/configs/react-router.config.js +36 -0
  20. package/dist/configs/react-router.config.js.map +1 -1
  21. package/dist/dependency-utils.js +14 -16
  22. package/dist/entry/server.d.ts.map +1 -1
  23. package/dist/entry/server.js +221 -11
  24. package/dist/entry/server.js.map +1 -1
  25. package/dist/generate-cartridge.js +106 -50
  26. package/dist/index.d.ts +127 -13
  27. package/dist/index.d.ts.map +1 -1
  28. package/dist/index.js +1147 -167
  29. package/dist/index.js.map +1 -1
  30. package/dist/local-dev-setup.js +13 -13
  31. package/dist/logger/index.d.ts +20 -0
  32. package/dist/logger/index.d.ts.map +1 -0
  33. package/dist/logger/index.js +69 -0
  34. package/dist/logger/index.js.map +1 -0
  35. package/dist/logger.js +79 -33
  36. package/dist/logger2.js +1 -0
  37. package/dist/manage-extensions.js +7 -13
  38. package/dist/mrt/ssr.mjs +60 -72
  39. package/dist/mrt/ssr.mjs.map +1 -1
  40. package/dist/mrt/streamingHandler.mjs +66 -78
  41. package/dist/mrt/streamingHandler.mjs.map +1 -1
  42. package/dist/react-router/Scripts.d.ts +1 -1
  43. package/dist/react-router/Scripts.d.ts.map +1 -1
  44. package/dist/react-router/Scripts.js +38 -2
  45. package/dist/react-router/Scripts.js.map +1 -1
  46. package/dist/server.js +296 -16
  47. package/dist/utils.js +4 -4
  48. package/dist/validate-cartridge.js +45 -0
  49. package/package.json +22 -5
@@ -1,50 +1,91 @@
1
- import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, r as SFNEXT_BASE_CARTRIDGE_NAME, t as CARTRIDGES_BASE_DIR } from "../config.js";
1
+ import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
2
+ import { Flags } from "@oclif/core";
2
3
  import path from "path";
3
4
  import fs from "fs-extra";
4
- import { InstanceCommand } from "@salesforce/b2c-tooling-sdk/cli";
5
- import { uploadCartridges } from "@salesforce/b2c-tooling-sdk/operations/code";
5
+ import { CartridgeCommand } from "@salesforce/b2c-tooling-sdk/cli";
6
+ import { deleteCartridges, getActiveCodeVersion, reloadCodeVersion, uploadCartridges } from "@salesforce/b2c-tooling-sdk/operations/code";
6
7
 
7
8
  //#region src/commands/deploy-cartridge.ts
8
9
  /**
9
- * Deploy cartridge command - deploys the Page Designer metadata cartridge to a B2C instance.
10
+ * Deploy cartridge command - deploys cartridges to a B2C instance.
10
11
  *
11
- * Inherits all B2C instance flags from InstanceCommand:
12
- * - --server/-s: B2C instance hostname (env: SFCC_SERVER)
13
- * - --code-version/-v: Code version (env: SFCC_CODE_VERSION)
14
- * - --username/-u: Basic auth username (env: SFCC_USERNAME)
15
- * - --password/-p: Basic auth password (env: SFCC_PASSWORD)
16
- * - --client-id: OAuth client ID (env: SFCC_CLIENT_ID)
17
- * - --client-secret: OAuth client secret (env: SFCC_CLIENT_SECRET)
18
- * - --webdav-server: Separate WebDAV hostname (env: SFCC_WEBDAV_SERVER)
19
- * - --config: Path to dw.json config file (env: SFCC_CONFIG)
20
- * - --instance/-i: Named instance from config (env: SFCC_INSTANCE)
12
+ * Extends CartridgeCommand which provides:
13
+ * - All B2C instance flags (--server, --code-version, --username, --password, etc.)
14
+ * - Cartridge filtering flags (-c/--cartridge, -x/--exclude-cartridge)
15
+ * - Cartridge discovery with plugin provider support
16
+ * - Config-driven cartridge filtering via dw.json `cartridges` option
17
+ *
18
+ * Additional flags:
19
+ * - --delete: Delete existing cartridges before upload
20
+ * - --reload/-r: Reload (re-activate) code version after deploy (requires OAuth)
21
21
  */
22
- var Deploy = class Deploy extends InstanceCommand {
23
- static description = "Deploy cartridge to B2C Commerce Cloud instance";
22
+ var Deploy = class Deploy extends CartridgeCommand {
23
+ static description = "Deploy cartridges to B2C Commerce Cloud instance";
24
24
  static examples = [
25
25
  "<%= config.bin %> <%= command.id %>",
26
26
  "<%= config.bin %> <%= command.id %> --project-directory ./my-project",
27
27
  "<%= config.bin %> <%= command.id %> -s my-sandbox.dx.commercecloud.salesforce.com",
28
- "<%= config.bin %> <%= command.id %> --code-version staging"
28
+ "<%= config.bin %> <%= command.id %> --code-version staging",
29
+ "<%= config.bin %> <%= command.id %> --delete",
30
+ "<%= config.bin %> <%= command.id %> --delete --reload",
31
+ "<%= config.bin %> <%= command.id %> -c app_storefrontnext_base",
32
+ "<%= config.bin %> <%= command.id %> -x test_cartridge"
29
33
  ];
30
- static flags = { ...InstanceCommand.baseFlags };
34
+ static flags = {
35
+ ...CartridgeCommand.baseFlags,
36
+ ...CartridgeCommand.cartridgeFlags,
37
+ reload: Flags.boolean({
38
+ char: "r",
39
+ description: "Reload (re-activate) code version after deploy",
40
+ default: false
41
+ }),
42
+ delete: Flags.boolean({
43
+ description: "Delete existing cartridges before upload",
44
+ default: false
45
+ })
46
+ };
31
47
  async run() {
32
48
  const { flags } = await this.parse(Deploy);
33
49
  const projectDirectory = flags["project-directory"] || process.cwd();
34
50
  if (!fs.existsSync(projectDirectory)) this.error(`Project directory doesn't exist: ${projectDirectory}`);
51
+ const cartridgesDir = path.join(projectDirectory, CARTRIDGES_BASE_DIR);
35
52
  const metadataDir = path.join(projectDirectory, CARTRIDGES_BASE_DIR, SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR);
36
53
  if (!fs.existsSync(metadataDir)) this.error(`Metadata directory doesn't exist: ${metadataDir}. Run 'sfnext generate-cartridge' first.`);
37
54
  this.requireServer();
38
- this.requireCodeVersion();
39
55
  this.requireWebDavCredentials();
40
- const cartridges = [{
41
- name: SFNEXT_BASE_CARTRIDGE_NAME,
42
- src: path.join(projectDirectory, CARTRIDGES_BASE_DIR, SFNEXT_BASE_CARTRIDGE_NAME),
43
- dest: SFNEXT_BASE_CARTRIDGE_NAME
44
- }];
45
- this.log(`Deploying cartridge "${SFNEXT_BASE_CARTRIDGE_NAME}" to code version "${this.resolvedConfig.values.codeVersion}"...`);
56
+ let version = this.resolvedConfig.values.codeVersion;
57
+ if ((!version || flags.reload) && !this.hasOAuthCredentials()) {
58
+ const reason = version ? "The --reload flag requires OAuth credentials to reload the code version via OCAPI." : "No code version specified. OAuth credentials are required to auto-discover the active code version.";
59
+ this.error(`${reason}\n\nProvide --code-version to use basic auth only, or configure OAuth credentials (--client-id and --client-secret).`);
60
+ }
61
+ if (!version) {
62
+ this.warn("No code version specified, discovering active code version...");
63
+ let activeVersion;
64
+ try {
65
+ activeVersion = await getActiveCodeVersion(this.instance);
66
+ } catch (error) {
67
+ this.error(`Failed to discover active code version: ${error instanceof Error ? error.message : String(error)}\n\nSpecify one explicitly with --code-version or in your dw.json config.`);
68
+ }
69
+ if (!activeVersion?.id) this.error("No active code version found. Specify one with --code-version or in your dw.json config.");
70
+ version = activeVersion.id;
71
+ this.instance.config.codeVersion = version;
72
+ }
73
+ const cartridges = await this.findCartridgesWithProviders(cartridgesDir);
74
+ if (cartridges.length === 0) this.error(`No cartridges found in ${cartridgesDir}`);
75
+ this.log(`Deploying to code version "${version}"...`);
76
+ for (const c of cartridges) this.log(` ${c.name} (${c.src})`);
77
+ if (flags.delete) {
78
+ this.log("Deleting existing cartridges...");
79
+ await deleteCartridges(this.instance, cartridges);
80
+ }
46
81
  await uploadCartridges(this.instance, cartridges);
47
- this.log(`Code deployed to version "${this.resolvedConfig.values.codeVersion}" successfully!`);
82
+ if (flags.reload) try {
83
+ await reloadCodeVersion(this.instance, version);
84
+ this.log("Code version reloaded.");
85
+ } catch (error) {
86
+ this.warn(`Could not reload code version: ${error instanceof Error ? error.message : String(error)}`);
87
+ }
88
+ this.log(`Deployed ${cartridges.length} cartridge(s) to version "${version}" successfully!`);
48
89
  }
49
90
  };
50
91
 
@@ -1,6 +1,8 @@
1
- import { a as printServerInfo, i as printServerConfig, o as printShutdownMessage } from "../logger.js";
1
+ import { i as printShutdownMessage, n as printServerConfig, r as printServerInfo } from "../logger.js";
2
+ import "../logger2.js";
2
3
  import { c as loadEnvFile } from "../utils.js";
3
- import { n as getCommerceCloudApiUrl, r as loadProjectConfig, t as createServer$2 } from "../server.js";
4
+ import { i as loadProjectConfig, n as initBasePathEnv, r as getCommerceCloudApiUrl, t as createServer$2 } from "../server.js";
5
+ import "../config.js";
4
6
  import { t as commonFlags } from "../flags.js";
5
7
  import { Command, Flags } from "@oclif/core";
6
8
  import path from "path";
@@ -48,6 +50,7 @@ async function dev(options = {}) {
48
50
  const port = options.port || 5173;
49
51
  process.env.NODE_ENV = process.env.NODE_ENV ?? "development";
50
52
  loadEnvFile(projectDir);
53
+ await initBasePathEnv(projectDir);
51
54
  process.env.EXTERNAL_DOMAIN_NAME = process.env.EXTERNAL_DOMAIN_NAME ?? `localhost:${port}`;
52
55
  const config = await loadProjectConfig(projectDir);
53
56
  const httpServer = createServer();
@@ -79,8 +82,7 @@ async function dev(options = {}) {
79
82
  proxyHost: getCommerceCloudApiUrl(config.commerce.api.shortCode, config.commerce.api.proxyHost),
80
83
  shortCode: config.commerce.api.shortCode,
81
84
  organizationId: config.commerce.api.organizationId,
82
- clientId: config.commerce.api.clientId,
83
- siteId: config.commerce.api.siteId
85
+ clientId: config.commerce.api.clientId
84
86
  });
85
87
  });
86
88
  ["SIGTERM", "SIGINT"].forEach((signal) => {
@@ -1,3 +1,5 @@
1
+ import "../../logger.js";
2
+ import "../../logger2.js";
1
3
  import "../../dependency-utils.js";
2
4
  import { t as createExtension } from "../../manage-extensions.js";
3
5
  import { Command, Flags } from "@oclif/core";
@@ -1,3 +1,5 @@
1
+ import "../../logger.js";
2
+ import "../../logger2.js";
1
3
  import "../../dependency-utils.js";
2
4
  import { t as commonFlags } from "../../flags.js";
3
5
  import { r as manageExtensions } from "../../manage-extensions.js";
@@ -21,11 +23,6 @@ var Install = class Install extends Command {
21
23
  char: "s",
22
24
  description: "Git URL of the source template project",
23
25
  default: DEFAULT_TEMPLATE_GIT_URL
24
- }),
25
- verbose: Flags.boolean({
26
- char: "v",
27
- description: "Verbose mode",
28
- default: false
29
26
  })
30
27
  };
31
28
  async run() {
@@ -34,8 +31,7 @@ var Install = class Install extends Command {
34
31
  projectDirectory: flags["project-directory"],
35
32
  install: true,
36
33
  extensions: flags.extension ? [flags.extension] : void 0,
37
- sourceGitUrl: flags["source-git-url"],
38
- verbose: flags.verbose
34
+ sourceGitUrl: flags["source-git-url"]
39
35
  });
40
36
  }
41
37
  };
@@ -1,3 +1,5 @@
1
+ import "../../logger.js";
2
+ import "../../logger2.js";
1
3
  import "../../dependency-utils.js";
2
4
  import { t as commonFlags } from "../../flags.js";
3
5
  import { n as listExtensions } from "../../manage-extensions.js";
@@ -1,3 +1,5 @@
1
+ import "../../logger.js";
2
+ import "../../logger2.js";
1
3
  import "../../dependency-utils.js";
2
4
  import { t as commonFlags } from "../../flags.js";
3
5
  import { r as manageExtensions } from "../../manage-extensions.js";
@@ -15,11 +17,6 @@ var Remove = class Remove extends Command {
15
17
  extensions: Flags.string({
16
18
  char: "e",
17
19
  description: "Comma-separated list of extension marker values (e.g. SFDC_EXT_STORE_LOCATOR,SFDC_EXT_THEME_SWITCHER)"
18
- }),
19
- verbose: Flags.boolean({
20
- char: "v",
21
- description: "Verbose mode",
22
- default: false
23
20
  })
24
21
  };
25
22
  async run() {
@@ -28,8 +25,7 @@ var Remove = class Remove extends Command {
28
25
  await manageExtensions({
29
26
  projectDirectory: flags["project-directory"],
30
27
  uninstall: true,
31
- extensions,
32
- verbose: flags.verbose
28
+ extensions
33
29
  });
34
30
  }
35
31
  };
@@ -1,9 +1,13 @@
1
- import { t as commonFlags } from "../flags.js";
1
+ import "../logger.js";
2
+ import "../logger2.js";
2
3
  import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
4
+ import { t as commonFlags } from "../flags.js";
3
5
  import { t as generateMetadata } from "../generate-cartridge.js";
6
+ import { t as validateCartridgeMetadata } from "../validate-cartridge.js";
4
7
  import { Command } from "@oclif/core";
5
8
  import path from "path";
6
9
  import fs from "fs-extra";
10
+ import chalk from "chalk";
7
11
 
8
12
  //#region src/commands/generate-cartridge.ts
9
13
  /**
@@ -27,7 +31,24 @@ var Generate = class Generate extends Command {
27
31
  }
28
32
  this.log("Generating Page Designer metadata...");
29
33
  await generateMetadata(projectDirectory, metadataDir);
30
- this.log("Page Designer metadata generated successfully!");
34
+ this.log("Page Designer metadata generated successfully!\n");
35
+ this.log("Validating generated metadata...\n");
36
+ const summary = await validateCartridgeMetadata(metadataDir);
37
+ for (const skipped of summary.skippedFiles) this.warn(`Skipping unrecognized file: ${skipped}`);
38
+ for (const result of summary.results) {
39
+ const relativePath = path.relative(metadataDir, result.filePath ?? "");
40
+ const typeInfo = result.schemaType ? ` (${result.schemaType})` : "";
41
+ if (result.valid) this.log(`${chalk.green("PASS")}: ${relativePath}${typeInfo}`);
42
+ else {
43
+ this.log(`${chalk.red("FAIL")}: ${relativePath}${typeInfo}`);
44
+ for (const error of result.errors) {
45
+ const location = error.path && error.path !== "/" ? ` at ${error.path}` : "";
46
+ this.log(` ${chalk.red("ERROR")}${location}: ${error.message}`);
47
+ }
48
+ }
49
+ }
50
+ this.log(`\n${summary.validFiles}/${summary.totalFiles} file(s) valid, ${summary.totalErrors} error(s)`);
51
+ if (summary.totalErrors > 0) this.error("Generated metadata has validation errors", { exit: 1 });
31
52
  }
32
53
  };
33
54
 
@@ -1,6 +1,8 @@
1
- import { a as printServerInfo, c as warn, i as printServerConfig, n as error, o as printShutdownMessage, r as info } from "../logger.js";
1
+ import { i as printShutdownMessage, n as printServerConfig, r as printServerInfo, t as logger } from "../logger.js";
2
+ import "../logger2.js";
2
3
  import { c as loadEnvFile } from "../utils.js";
3
- import { n as getCommerceCloudApiUrl, r as loadProjectConfig, t as createServer } from "../server.js";
4
+ import { i as loadProjectConfig, n as initBasePathEnv, r as getCommerceCloudApiUrl, t as createServer } from "../server.js";
5
+ import "../config.js";
4
6
  import { t as commonFlags } from "../flags.js";
5
7
  import { Command, Flags } from "@oclif/core";
6
8
  import { execSync } from "child_process";
@@ -13,32 +15,36 @@ import { pathToFileURL } from "url";
13
15
  * Start the preview server with production build
14
16
  */
15
17
  async function preview(options = {}) {
18
+ process.setSourceMapsEnabled(true);
19
+ process.env.NODE_OPTIONS = [process.env.NODE_OPTIONS, "--enable-source-maps"].filter(Boolean).join(" ");
16
20
  const startTime = Date.now();
17
21
  const projectDir = path.resolve(options.projectDirectory || process.cwd());
18
22
  const port = options.port || 3e3;
19
23
  process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
20
24
  process.env.EXTERNAL_DOMAIN_NAME = process.env.EXTERNAL_DOMAIN_NAME ?? `localhost:${port}`;
21
25
  loadEnvFile(projectDir);
26
+ await initBasePathEnv(projectDir);
22
27
  const buildPath = path.join(projectDir, "build", "server", "index.js");
23
28
  if (!fs.existsSync(buildPath)) {
24
- warn("Production build not found. Building project...");
25
- info("Running: pnpm build");
29
+ logger.warn("Production build not found. Building project...");
30
+ logger.info("Running: pnpm build");
26
31
  try {
27
32
  execSync("pnpm build", {
28
33
  cwd: projectDir,
29
34
  stdio: "inherit"
30
35
  });
31
- info("Build completed successfully");
36
+ logger.info("Build completed successfully");
32
37
  } catch (err) {
33
- error(`Build failed: ${err instanceof Error ? err.message : String(err)}`);
38
+ const errorMsg = err instanceof Error ? err.message : String(err);
39
+ logger.error(`Build failed: ${errorMsg}`);
34
40
  process.exit(1);
35
41
  }
36
42
  if (!fs.existsSync(buildPath)) {
37
- error(`Build still not found at ${buildPath} after running build command`);
43
+ logger.error(`Build still not found at ${buildPath} after running build command`);
38
44
  process.exit(1);
39
45
  }
40
46
  }
41
- info(`Loading production build from ${buildPath}`);
47
+ logger.info(`Loading production build from ${buildPath}`);
42
48
  const build = (await import(pathToFileURL(buildPath).href)).default;
43
49
  const config = await loadProjectConfig(projectDir);
44
50
  const server = (await createServer({
@@ -59,8 +65,7 @@ async function preview(options = {}) {
59
65
  proxyHost: getCommerceCloudApiUrl(config.commerce.api.shortCode),
60
66
  shortCode: config.commerce.api.shortCode,
61
67
  organizationId: config.commerce.api.organizationId,
62
- clientId: config.commerce.api.clientId,
63
- siteId: config.commerce.api.siteId
68
+ clientId: config.commerce.api.clientId
64
69
  });
65
70
  });
66
71
  ["SIGTERM", "SIGINT"].forEach((signal) => {
@@ -1,13 +1,13 @@
1
1
  import "../logger.js";
2
+ import "../logger2.js";
2
3
  import { n as getDefaultBuildDir, r as getDefaultMessage } from "../utils.js";
3
- import { t as createBundle } from "../bundle.js";
4
4
  import { a as buildMrtConfig, i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, n as GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH, r as SFNEXT_BASE_CARTRIDGE_NAME, t as CARTRIDGES_BASE_DIR } from "../config.js";
5
+ import { t as createBundle } from "../bundle.js";
5
6
  import { t as generateMetadata } from "../generate-cartridge.js";
6
7
  import { Flags } from "@oclif/core";
7
8
  import path from "path";
8
9
  import fs from "fs-extra";
9
10
  import { MrtCommand } from "@salesforce/b2c-tooling-sdk/cli";
10
- import { resolveConfig } from "@salesforce/b2c-tooling-sdk/config";
11
11
  import { uploadCartridges } from "@salesforce/b2c-tooling-sdk/operations/code";
12
12
  import { uploadBundle, waitForEnv } from "@salesforce/b2c-tooling-sdk/operations/mrt";
13
13
  import { DEFAULT_MRT_ORIGIN, createMrtClient } from "@salesforce/b2c-tooling-sdk/clients";
@@ -17,10 +17,10 @@ import { DEFAULT_MRT_ORIGIN, createMrtClient } from "@salesforce/b2c-tooling-sdk
17
17
  * MRT Push command - builds and pushes bundle to Managed Runtime.
18
18
  *
19
19
  * Inherits MRT flags from MrtCommand:
20
- * - --api-key: MRT API key (env: SFCC_MRT_API_KEY)
21
- * - --project/-p: MRT project slug (env: SFCC_MRT_PROJECT)
22
- * - --environment/-e: MRT target environment (env: SFCC_MRT_ENVIRONMENT)
23
- * - --cloud-origin: MRT cloud origin URL (env: SFCC_MRT_CLOUD_ORIGIN)
20
+ * - --api-key: MRT API key (env: MRT_API_KEY, fallback: SFCC_MRT_API_KEY)
21
+ * - --project/-p: MRT project slug (env: MRT_PROJECT, fallback: SFCC_MRT_PROJECT)
22
+ * - --environment/-e: MRT target environment (env: MRT_TARGET, fallback: SFCC_MRT_ENVIRONMENT)
23
+ * - --cloud-origin: MRT cloud origin URL (env: MRT_CLOUD_ORIGIN, fallback: SFCC_MRT_CLOUD_ORIGIN)
24
24
  * - --credentials-file: Path to MRT credentials file (env: MRT_CREDENTIALS_FILE)
25
25
  * - --config: Path to dw.json config file (env: SFCC_CONFIG)
26
26
  * - --instance/-i: Named instance from config (env: SFCC_INSTANCE)
@@ -65,16 +65,16 @@ var Push = class Push extends MrtCommand {
65
65
  if (flags["project-slug"]) this.warn("Flag --project-slug is deprecated. Use --project instead.");
66
66
  if (flags.target) this.warn("Flag --target is deprecated. Use --environment instead.");
67
67
  const target = flags.environment || flags.target || this.resolvedConfig.values.mrtEnvironment;
68
- if (flags.wait && !target) this.error("You must provide a target environment when using --wait (via --environment flag, SFCC_MRT_ENVIRONMENT env var, or dw.json)");
68
+ if (flags.wait && !target) this.error("You must provide a target environment when using --wait (via --environment flag, MRT_TARGET env var, or dw.json)");
69
69
  if (!fs.existsSync(projectDirectory)) this.error(`Project directory "${projectDirectory}" does not exist!`);
70
70
  const projectSlug = flags.project || flags["project-slug"] || this.resolvedConfig.values.mrtProject;
71
- if (!projectSlug || projectSlug.trim() === "") this.error("Project slug is required. Provide --project, set SFCC_MRT_PROJECT env var, or configure mrtProject in dw.json");
71
+ if (!projectSlug || projectSlug.trim() === "") this.error("Project slug is required. Provide --project, set MRT_PROJECT env var, or configure mrtProject in dw.json");
72
72
  const buildDirectory = flags["build-directory"] ?? getDefaultBuildDir(projectDirectory);
73
73
  if (!fs.existsSync(buildDirectory)) this.error(`Build directory "${buildDirectory}" does not exist!`);
74
74
  if (GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH) await this.generateAndDeployCartridge(projectDirectory);
75
75
  if (target) process.env.DEPLOY_TARGET = target;
76
76
  this.requireMrtCredentials();
77
- const config = buildMrtConfig(buildDirectory, projectDirectory);
77
+ const config = await buildMrtConfig(buildDirectory, projectDirectory);
78
78
  const message = flags.message ?? getDefaultMessage(projectDirectory);
79
79
  this.log(`Creating bundle for project: ${projectSlug}`);
80
80
  if (target) this.log(`Target environment: ${target}`);
@@ -89,18 +89,25 @@ var Push = class Push extends MrtCommand {
89
89
  });
90
90
  const origin = this.resolvedConfig.values.mrtOrigin || DEFAULT_MRT_ORIGIN;
91
91
  const client = createMrtClient({ origin }, this.getMrtAuth());
92
- this.log(`Beginning upload to ${origin}`);
92
+ this.log(`Uploading bundle to ${origin}`);
93
93
  const result = await uploadBundle(client, projectSlug, bundle, target);
94
+ this.log(`Bundle ${result.bundleId} uploaded`);
94
95
  if (flags.wait && target) {
95
- this.log("Bundle uploaded - waiting for deployment to complete");
96
+ this.log(`Waiting for deployment to ${target}...`);
97
+ let lastState = "";
96
98
  await waitForEnv({
97
99
  projectSlug,
98
100
  slug: target,
99
- origin
101
+ origin,
102
+ onPoll: (info) => {
103
+ if (info.state !== lastState) {
104
+ lastState = info.state;
105
+ this.log(` ${target}: ${info.state} (${info.elapsedSeconds}s)`);
106
+ }
107
+ }
100
108
  }, this.getMrtAuth());
101
- this.log("Deployment complete!");
102
- } else this.log("Bundle uploaded successfully!");
103
- this.log(`Bundle ID: ${result.bundleId}`);
109
+ this.log(`Deployment complete — bundle ${result.bundleId} is live on ${target}`);
110
+ }
104
111
  }
105
112
  /**
106
113
  * Generate and deploy cartridge metadata to B2C instance.
@@ -114,16 +121,15 @@ var Push = class Push extends MrtCommand {
114
121
  await generateMetadata(projectDirectory, metadataDir);
115
122
  this.log("Cartridge metadata generated successfully!");
116
123
  this.log("Deploying cartridge to Commerce Cloud...");
117
- const b2cConfig = resolveConfig({}, { workingDirectory: projectDirectory });
118
- if (!b2cConfig.hasB2CInstanceConfig()) {
124
+ if (!this.resolvedConfig.hasB2CInstanceConfig()) {
119
125
  this.warn("B2C instance not configured, skipping cartridge deployment");
120
126
  return;
121
127
  }
122
- if (!b2cConfig.values.codeVersion) {
128
+ if (!this.resolvedConfig.values.codeVersion) {
123
129
  this.warn("Code version not configured, skipping cartridge deployment");
124
130
  return;
125
131
  }
126
- await uploadCartridges(b2cConfig.createB2CInstance(), [{
132
+ await uploadCartridges(this.resolvedConfig.createB2CInstance(), [{
127
133
  name: SFNEXT_BASE_CARTRIDGE_NAME,
128
134
  src: path.join(projectDirectory, CARTRIDGES_BASE_DIR, SFNEXT_BASE_CARTRIDGE_NAME),
129
135
  dest: SFNEXT_BASE_CARTRIDGE_NAME
@@ -0,0 +1,51 @@
1
+ import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
2
+ import { t as commonFlags } from "../flags.js";
3
+ import { t as validateCartridgeMetadata } from "../validate-cartridge.js";
4
+ import { Command } from "@oclif/core";
5
+ import path from "path";
6
+ import fs from "fs-extra";
7
+ import chalk from "chalk";
8
+
9
+ //#region src/commands/validate-cartridge.ts
10
+ /**
11
+ * Validate cartridge metadata command.
12
+ *
13
+ * Validates all Page Designer metadata JSON files in the cartridge
14
+ * directory against their metadefinition schemas.
15
+ */
16
+ var ValidateCartridge = class ValidateCartridge extends Command {
17
+ static description = "Validate Page Designer metadata JSON files against schemas";
18
+ static examples = ["<%= config.bin %> <%= command.id %>", "<%= config.bin %> <%= command.id %> -d ./my-project"];
19
+ static flags = { ...commonFlags };
20
+ async run() {
21
+ const { flags } = await this.parse(ValidateCartridge);
22
+ const projectDirectory = flags["project-directory"];
23
+ if (!fs.existsSync(projectDirectory)) this.error(`Project directory doesn't exist: ${projectDirectory}`);
24
+ const metadataDir = path.join(projectDirectory, CARTRIDGES_BASE_DIR, SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR);
25
+ if (!fs.existsSync(metadataDir)) this.error(`Metadata directory doesn't exist: ${metadataDir}\nRun "sfnext generate-cartridge" first to generate metadata files.`);
26
+ this.log("Validating Page Designer metadata...\n");
27
+ const summary = await validateCartridgeMetadata(metadataDir);
28
+ if (summary.totalFiles === 0) {
29
+ this.warn("No metadata files found to validate.");
30
+ return;
31
+ }
32
+ for (const skipped of summary.skippedFiles) this.warn(`Skipping unrecognized file: ${skipped}`);
33
+ for (const result of summary.results) {
34
+ const relativePath = path.relative(metadataDir, result.filePath ?? "");
35
+ const typeInfo = result.schemaType ? ` (${result.schemaType})` : "";
36
+ if (result.valid) this.log(`${chalk.green("PASS")}: ${relativePath}${typeInfo}`);
37
+ else {
38
+ this.log(`${chalk.red("FAIL")}: ${relativePath}${typeInfo}`);
39
+ for (const error of result.errors) {
40
+ const location = error.path && error.path !== "/" ? ` at ${error.path}` : "";
41
+ this.log(` ${chalk.red("ERROR")}${location}: ${error.message}`);
42
+ }
43
+ }
44
+ }
45
+ this.log(`\n${summary.validFiles}/${summary.totalFiles} file(s) valid, ${summary.totalErrors} error(s)`);
46
+ if (summary.totalErrors > 0) this.error("Validation failed", { exit: 1 });
47
+ }
48
+ };
49
+
50
+ //#endregion
51
+ export { ValidateCartridge as default };
package/dist/config.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { existsSync } from "node:fs";
2
+ import { resolve } from "node:path";
3
+
1
4
  //#region src/mrt/utils.ts
2
5
  const MRT_BUNDLE_TYPE_SSR = "ssr";
3
6
  const MRT_STREAMING_ENTRY_FILE = "streamingHandler";
@@ -29,59 +32,83 @@ const SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR = `${SFNEXT_BASE_CARTRIDGE_NAME}/cartridg
29
32
  */
30
33
  const GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH = false;
31
34
  /**
32
- * Build MRT SSR configuration for bundle deployment
35
+ * Merge override patterns with defaults while preserving required defaults.
36
+ *
37
+ * Overrides are prepended so they can add additional globs or exclusions, while
38
+ * defaults remain in the list to prevent breaking essential artifacts.
33
39
  *
34
- * Defines which files should be:
35
- * - Server-only (ssrOnly): Deployed only to Lambda functions
36
- * - Shared (ssrShared): Deployed to both Lambda and CDN
40
+ * @example
41
+ * const defaults = ['server/**', 'loader.js'];
42
+ * const overrides = ['custom/**', '!static/**'];
43
+ * mergePatterns(defaults, overrides);
44
+ * // => ['custom/**', '!static/**', 'server/**', 'loader.js']
45
+ */
46
+ const mergePatterns = (defaults, overrides) => {
47
+ if (!overrides?.length) return defaults;
48
+ return Array.from(new Set([...overrides, ...defaults]));
49
+ };
50
+ /**
51
+ * Load runtime config from config.server.ts for MRT bundle settings.
37
52
  *
38
- * @param buildDirectory - Path to the build output directory
39
- * @param projectDirectory - Path to the project root (reserved for future use)
40
- * @returns MRT SSR configuration with glob patterns
53
+ * Keep in sync with @salesforce/storefront-next-runtime/src/config/load-config.ts.
41
54
  */
42
- const buildMrtConfig = (_buildDirectory, _projectDirectory) => {
55
+ async function loadRuntimeConfig(projectDirectory) {
56
+ if (!projectDirectory) return;
57
+ const configPath = resolve(projectDirectory, "config.server.ts");
58
+ if (!existsSync(configPath)) return;
59
+ try {
60
+ const { createJiti } = await import("jiti");
61
+ const mod = await createJiti(import.meta.url, {
62
+ fsCache: false,
63
+ interopDefault: true
64
+ }).import(configPath);
65
+ return (mod.default ?? mod).runtime;
66
+ } catch (error) {
67
+ throw new Error(`[storefront-next-dev] Found config.server.ts at ${configPath} but failed to import it.`, { cause: error });
68
+ }
69
+ }
70
+ const buildMrtConfig = async (_buildDirectory, projectDirectory) => {
43
71
  const ssrEntryPoint = getMrtEntryFile("production");
72
+ const defaultSsrOnly = [
73
+ "server/**/*",
74
+ "package.json",
75
+ "loader.js",
76
+ `${ssrEntryPoint}.{js,mjs,cjs}`,
77
+ `${ssrEntryPoint}.{js,mjs,cjs}.map`,
78
+ "!static/**/*",
79
+ "sfnext-server-*.mjs",
80
+ "sfnext-server-*.mjs.map"
81
+ ];
82
+ const defaultSsrShared = [
83
+ "client/**/*",
84
+ "static/**/*",
85
+ "**/*.css",
86
+ "**/*.png",
87
+ "**/*.jpg",
88
+ "**/*.jpeg",
89
+ "**/*.gif",
90
+ "**/*.svg",
91
+ "**/*.ico",
92
+ "**/*.woff",
93
+ "**/*.woff2",
94
+ "**/*.ttf",
95
+ "**/*.eot"
96
+ ];
97
+ const defaultSsrParameters = { ssrFunctionNodeVersion: "24.x" };
98
+ const runtimeConfig = await loadRuntimeConfig(projectDirectory);
99
+ const ssrOnly = mergePatterns(defaultSsrOnly, runtimeConfig?.ssrOnly);
100
+ const ssrShared = mergePatterns(defaultSsrShared, runtimeConfig?.ssrShared);
101
+ const ssrParameters = {
102
+ ...defaultSsrParameters,
103
+ ...runtimeConfig?.ssrParameters ?? {}
104
+ };
105
+ if (!ssrParameters.envBasePath) delete ssrParameters.envBasePath;
44
106
  return {
45
- ssrOnly: [
46
- "server/**/*",
47
- "loader.js",
48
- `${ssrEntryPoint}.{js,mjs,cjs}`,
49
- `${ssrEntryPoint}.{js,mjs,cjs}.map`,
50
- "!static/**/*",
51
- "sfnext-server-*.mjs",
52
- "!**/*.stories.tsx",
53
- "!**/*.stories.ts",
54
- "!**/*-snapshot.tsx",
55
- "!.storybook/**/*",
56
- "!storybook-static/**/*",
57
- "!**/__mocks__/**/*",
58
- "!**/__snapshots__/**/*"
59
- ],
60
- ssrShared: [
61
- "client/**/*",
62
- "static/**/*",
63
- "**/*.css",
64
- "**/*.png",
65
- "**/*.jpg",
66
- "**/*.jpeg",
67
- "**/*.gif",
68
- "**/*.svg",
69
- "**/*.ico",
70
- "**/*.woff",
71
- "**/*.woff2",
72
- "**/*.ttf",
73
- "**/*.eot",
74
- "!**/*.stories.tsx",
75
- "!**/*.stories.ts",
76
- "!**/*-snapshot.tsx",
77
- "!.storybook/**/*",
78
- "!storybook-static/**/*",
79
- "!**/__mocks__/**/*",
80
- "!**/__snapshots__/**/*"
81
- ],
82
- ssrParameters: { ssrFunctionNodeVersion: "24.x" }
107
+ ssrOnly,
108
+ ssrShared,
109
+ ssrParameters
83
110
  };
84
111
  };
85
112
 
86
113
  //#endregion
87
- export { buildMrtConfig as a, SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR as i, GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH as n, SFNEXT_BASE_CARTRIDGE_NAME as r, CARTRIDGES_BASE_DIR as t };
114
+ export { buildMrtConfig as a, SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR as i, GENERATE_AND_DEPLOY_CARTRIDGE_ON_MRT_PUSH as n, loadRuntimeConfig as o, SFNEXT_BASE_CARTRIDGE_NAME as r, CARTRIDGES_BASE_DIR as t };
@@ -1 +1 @@
1
- {"version":3,"file":"react-router.config.d.ts","names":[],"sources":["../../src/configs/react-router.config.ts"],"sourcesContent":[],"mappings":";;;;;;;;;iBAsBgB,oBAAA,CAAA,GAAwB"}
1
+ {"version":3,"file":"react-router.config.d.ts","names":[],"sources":["../../src/configs/react-router.config.ts"],"sourcesContent":[],"mappings":";;;;;;;;;iBAuBgB,oBAAA,CAAA,GAAwB"}