@salesforce/storefront-next-dev 0.1.1 → 0.2.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 (50) hide show
  1. package/README.md +45 -36
  2. package/bin/run.js +12 -0
  3. package/dist/bundle.js +83 -0
  4. package/dist/cartridge-services/index.d.ts +2 -26
  5. package/dist/cartridge-services/index.d.ts.map +1 -1
  6. package/dist/cartridge-services/index.js +3 -336
  7. package/dist/cartridge-services/index.js.map +1 -1
  8. package/dist/commands/create-bundle.js +107 -0
  9. package/dist/commands/create-instructions.js +174 -0
  10. package/dist/commands/create-storefront.js +210 -0
  11. package/dist/commands/deploy-cartridge.js +52 -0
  12. package/dist/commands/dev.js +122 -0
  13. package/dist/commands/extensions/create.js +38 -0
  14. package/dist/commands/extensions/install.js +44 -0
  15. package/dist/commands/extensions/list.js +21 -0
  16. package/dist/commands/extensions/remove.js +38 -0
  17. package/dist/commands/generate-cartridge.js +35 -0
  18. package/dist/commands/prepare-local.js +30 -0
  19. package/dist/commands/preview.js +101 -0
  20. package/dist/commands/push.js +139 -0
  21. package/dist/config.js +87 -0
  22. package/dist/configs/react-router.config.js +3 -1
  23. package/dist/configs/react-router.config.js.map +1 -1
  24. package/dist/dependency-utils.js +314 -0
  25. package/dist/entry/client.d.ts +1 -0
  26. package/dist/entry/client.js +28 -0
  27. package/dist/entry/client.js.map +1 -0
  28. package/dist/entry/server.d.ts +15 -0
  29. package/dist/entry/server.d.ts.map +1 -0
  30. package/dist/entry/server.js +35 -0
  31. package/dist/entry/server.js.map +1 -0
  32. package/dist/flags.js +11 -0
  33. package/dist/generate-cartridge.js +620 -0
  34. package/dist/hooks/init.js +47 -0
  35. package/dist/index.d.ts +9 -29
  36. package/dist/index.d.ts.map +1 -1
  37. package/dist/index.js +413 -621
  38. package/dist/index.js.map +1 -1
  39. package/dist/local-dev-setup.js +176 -0
  40. package/dist/logger.js +105 -0
  41. package/dist/manage-extensions.js +329 -0
  42. package/dist/mrt/ssr.mjs +3 -3
  43. package/dist/mrt/ssr.mjs.map +1 -1
  44. package/dist/mrt/streamingHandler.mjs +4 -4
  45. package/dist/mrt/streamingHandler.mjs.map +1 -1
  46. package/dist/server.js +425 -0
  47. package/dist/utils.js +126 -0
  48. package/package.json +44 -9
  49. package/dist/cli.js +0 -3393
  50. /package/{LICENSE.txt → LICENSE} +0 -0
package/dist/server.js ADDED
@@ -0,0 +1,425 @@
1
+ import { c as warn, r as info } from "./logger.js";
2
+ import path from "path";
3
+ import chalk from "chalk";
4
+ import express from "express";
5
+ import { createRequestHandler } from "@react-router/express";
6
+ import { existsSync, readFileSync } from "node:fs";
7
+ import { resolve } from "node:path";
8
+ import { pathToFileURL } from "node:url";
9
+ import { createProxyMiddleware } from "http-proxy-middleware";
10
+ import compression from "compression";
11
+ import zlib from "node:zlib";
12
+ import morgan from "morgan";
13
+ import { minimatch } from "minimatch";
14
+
15
+ //#region src/server/ts-import.ts
16
+ /**
17
+ * Parse TypeScript paths from tsconfig.json and convert to jiti alias format.
18
+ *
19
+ * @param tsconfigPath - Path to tsconfig.json
20
+ * @param projectDirectory - Project root directory for resolving relative paths
21
+ * @returns Record of alias mappings for jiti
22
+ *
23
+ * @example
24
+ * // tsconfig.json: { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
25
+ * // Returns: { "@/": "/absolute/path/to/src/" }
26
+ */
27
+ function parseTsconfigPaths(tsconfigPath, projectDirectory) {
28
+ const alias = {};
29
+ if (!existsSync(tsconfigPath)) return alias;
30
+ try {
31
+ const tsconfigContent = readFileSync(tsconfigPath, "utf-8");
32
+ const tsconfig = JSON.parse(tsconfigContent);
33
+ const paths = tsconfig.compilerOptions?.paths;
34
+ const baseUrl = tsconfig.compilerOptions?.baseUrl || ".";
35
+ if (paths) {
36
+ for (const [key, values] of Object.entries(paths)) if (values && values.length > 0) {
37
+ const aliasKey = key.replace(/\/\*$/, "/");
38
+ alias[aliasKey] = resolve(projectDirectory, baseUrl, values[0].replace(/\/\*$/, "/").replace(/^\.\//, ""));
39
+ }
40
+ }
41
+ } catch {}
42
+ const sortedAlias = {};
43
+ Object.keys(alias).sort((a, b) => b.length - a.length).forEach((key) => {
44
+ sortedAlias[key] = alias[key];
45
+ });
46
+ return sortedAlias;
47
+ }
48
+ /**
49
+ * Import a TypeScript file using jiti with proper path alias resolution.
50
+ * This is a cross-platform alternative to tsx that works on Windows.
51
+ *
52
+ * @param filePath - Absolute path to the TypeScript file to import
53
+ * @param options - Import options including project directory
54
+ * @returns The imported module
55
+ */
56
+ async function importTypescript(filePath, options) {
57
+ const { projectDirectory, tsconfigPath = resolve(projectDirectory, "tsconfig.json") } = options;
58
+ const { createJiti } = await import("jiti");
59
+ const alias = parseTsconfigPaths(tsconfigPath, projectDirectory);
60
+ return createJiti(import.meta.url, {
61
+ fsCache: false,
62
+ interopDefault: true,
63
+ alias
64
+ }).import(filePath);
65
+ }
66
+
67
+ //#endregion
68
+ //#region src/server/config.ts
69
+ /**
70
+ * This is a temporary function before we move the config implementation from
71
+ * template-retail-rsc-app to the SDK.
72
+ *
73
+ * @ TODO: Remove this function after we move the config implementation from
74
+ * template-retail-rsc-app to the SDK.
75
+ *
76
+ */
77
+ function loadConfigFromEnv() {
78
+ const shortCode = process.env.PUBLIC__app__commerce__api__shortCode;
79
+ const organizationId = process.env.PUBLIC__app__commerce__api__organizationId;
80
+ const clientId = process.env.PUBLIC__app__commerce__api__clientId;
81
+ const siteId = process.env.PUBLIC__app__commerce__api__siteId;
82
+ const proxy = process.env.PUBLIC__app__commerce__api__proxy || "/mobify/proxy/api";
83
+ const proxyHost = process.env.SCAPI_PROXY_HOST;
84
+ if (!shortCode && !proxyHost) throw new Error("Missing PUBLIC__app__commerce__api__shortCode environment variable.\nPlease set it in your .env file or environment.");
85
+ if (!organizationId) throw new Error("Missing PUBLIC__app__commerce__api__organizationId environment variable.\nPlease set it in your .env file or environment.");
86
+ if (!clientId) throw new Error("Missing PUBLIC__app__commerce__api__clientId environment variable.\nPlease set it in your .env file or environment.");
87
+ if (!siteId) throw new Error("Missing PUBLIC__app__commerce__api__siteId environment variable.\nPlease set it in your .env file or environment.");
88
+ return { commerce: { api: {
89
+ shortCode: shortCode || "",
90
+ organizationId,
91
+ clientId,
92
+ siteId,
93
+ proxy,
94
+ proxyHost
95
+ } } };
96
+ }
97
+ /**
98
+ * Load storefront-next project configuration from config.server.ts.
99
+ * Requires projectDirectory to be provided.
100
+ *
101
+ * @param projectDirectory - Project directory to load config.server.ts from
102
+ * @throws Error if config.server.ts is not found or invalid
103
+ */
104
+ async function loadProjectConfig(projectDirectory) {
105
+ const configPath = resolve(projectDirectory, "config.server.ts");
106
+ const tsconfigPath = resolve(projectDirectory, "tsconfig.json");
107
+ if (!existsSync(configPath)) throw new Error(`config.server.ts not found at ${configPath}.\nPlease ensure config.server.ts exists in your project root.`);
108
+ const config = (await importTypescript(configPath, {
109
+ projectDirectory,
110
+ tsconfigPath
111
+ })).default;
112
+ if (!config?.app?.commerce?.api) throw new Error("Invalid config.server.ts: missing app.commerce.api configuration.\nPlease ensure your config.server.ts has the commerce API configuration.");
113
+ const api = config.app.commerce.api;
114
+ const proxyHost = process.env.SCAPI_PROXY_HOST;
115
+ if (!api.shortCode && !proxyHost) throw new Error("Missing shortCode in config.server.ts commerce.api configuration");
116
+ if (!api.organizationId) throw new Error("Missing organizationId in config.server.ts commerce.api configuration");
117
+ if (!api.clientId) throw new Error("Missing clientId in config.server.ts commerce.api configuration");
118
+ if (!api.siteId) throw new Error("Missing siteId in config.server.ts commerce.api configuration");
119
+ return { commerce: { api: {
120
+ shortCode: api.shortCode || "",
121
+ organizationId: api.organizationId,
122
+ clientId: api.clientId,
123
+ siteId: api.siteId,
124
+ proxy: api.proxy || "/mobify/proxy/api",
125
+ proxyHost
126
+ } } };
127
+ }
128
+
129
+ //#endregion
130
+ //#region src/utils/paths.ts
131
+ /**
132
+ * Get the Commerce Cloud API URL from a short code
133
+ */
134
+ function getCommerceCloudApiUrl(shortCode, proxyHost) {
135
+ return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;
136
+ }
137
+ /**
138
+ * Get the bundle path for static assets
139
+ */
140
+ function getBundlePath(bundleId) {
141
+ return `/mobify/bundle/${bundleId}/client/`;
142
+ }
143
+
144
+ //#endregion
145
+ //#region src/server/middleware/proxy.ts
146
+ /**
147
+ * Create proxy middleware for Commerce Cloud API
148
+ * Proxies requests from /mobify/proxy/api to the Commerce Cloud API
149
+ */
150
+ function createCommerceProxyMiddleware(config) {
151
+ return createProxyMiddleware({
152
+ target: getCommerceCloudApiUrl(config.commerce.api.shortCode, config.commerce.api.proxyHost),
153
+ changeOrigin: true,
154
+ secure: !config.commerce.api.proxyHost
155
+ });
156
+ }
157
+
158
+ //#endregion
159
+ //#region src/server/middleware/static.ts
160
+ /**
161
+ * Create static file serving middleware for client assets
162
+ * Serves files from build/client at /mobify/bundle/{BUNDLE_ID}/client/
163
+ */
164
+ function createStaticMiddleware(bundleId, projectDirectory) {
165
+ const bundlePath = getBundlePath(bundleId);
166
+ const clientBuildDir = path.join(projectDirectory, "build", "client");
167
+ info(`Serving static assets from ${clientBuildDir} at ${bundlePath}`);
168
+ return express.static(clientBuildDir, { setHeaders: (res) => {
169
+ res.setHeader("Cache-Control", "public, max-age=31536000, immutable");
170
+ res.setHeader("x-local-static-cache-control", "1");
171
+ } });
172
+ }
173
+
174
+ //#endregion
175
+ //#region src/server/middleware/compression.ts
176
+ /**
177
+ * Parse and validate COMPRESSION_LEVEL environment variable
178
+ * @returns Valid compression level (0-9) or default compression level
179
+ */
180
+ function getCompressionLevel() {
181
+ const raw = process.env.COMPRESSION_LEVEL;
182
+ const DEFAULT = zlib.constants.Z_DEFAULT_COMPRESSION;
183
+ if (raw == null || raw.trim() === "") return DEFAULT;
184
+ const level = Number(raw);
185
+ if (!(Number.isInteger(level) && level >= 0 && level <= 9)) {
186
+ warn(`[compression] Invalid COMPRESSION_LEVEL="${raw}". Using default (${DEFAULT}).`);
187
+ return DEFAULT;
188
+ }
189
+ return level;
190
+ }
191
+ /**
192
+ * Create compression middleware for gzip/brotli compression
193
+ * Used in preview mode to optimize response sizes
194
+ */
195
+ function createCompressionMiddleware() {
196
+ return compression({
197
+ filter: (req, res) => {
198
+ if (req.headers["x-no-compression"]) return false;
199
+ return compression.filter(req, res);
200
+ },
201
+ level: getCompressionLevel()
202
+ });
203
+ }
204
+
205
+ //#endregion
206
+ //#region src/server/middleware/logging.ts
207
+ /**
208
+ * Patterns for URLs to skip logging (static assets and Vite internals)
209
+ */
210
+ const SKIP_PATTERNS = [
211
+ "/@vite/**",
212
+ "/@id/**",
213
+ "/@fs/**",
214
+ "/@react-router/**",
215
+ "/src/**",
216
+ "/node_modules/**",
217
+ "**/*.js",
218
+ "**/*.css",
219
+ "**/*.ts",
220
+ "**/*.tsx",
221
+ "**/*.js.map",
222
+ "**/*.css.map"
223
+ ];
224
+ /**
225
+ * Create request logging middleware
226
+ * Used in dev and preview modes for request visibility
227
+ */
228
+ function createLoggingMiddleware() {
229
+ morgan.token("status-colored", (req, res) => {
230
+ const status = res.statusCode;
231
+ let color = chalk.green;
232
+ if (status >= 500) color = chalk.red;
233
+ else if (status >= 400) color = chalk.yellow;
234
+ else if (status >= 300) color = chalk.cyan;
235
+ return color(String(status));
236
+ });
237
+ morgan.token("method-colored", (req) => {
238
+ const method = req.method;
239
+ const colors = {
240
+ GET: chalk.green,
241
+ POST: chalk.blue,
242
+ PUT: chalk.yellow,
243
+ DELETE: chalk.red,
244
+ PATCH: chalk.magenta
245
+ };
246
+ return (method && colors[method] || chalk.white)(method);
247
+ });
248
+ return morgan((tokens, req, res) => {
249
+ return [
250
+ chalk.gray("["),
251
+ tokens["method-colored"](req, res),
252
+ chalk.gray("]"),
253
+ tokens.url(req, res),
254
+ "-",
255
+ tokens["status-colored"](req, res),
256
+ chalk.gray(`(${tokens["response-time"](req, res)}ms)`)
257
+ ].join(" ");
258
+ }, { skip: (req) => {
259
+ return SKIP_PATTERNS.some((pattern) => minimatch(req.url, pattern, { dot: true }));
260
+ } });
261
+ }
262
+
263
+ //#endregion
264
+ //#region src/server/middleware/host-header.ts
265
+ /**
266
+ * Normalizes the X-Forwarded-Host header to support React Router's CSRF validation features.
267
+ *
268
+ * NOTE: This middleware performs header manipulation as a temporary, internal
269
+ * solution for MRT/Lambda environments. It may be updated or removed if React Router
270
+ * introduces a first-class configuration for validating against forwarded headers.
271
+ *
272
+ * React Router v7.12+ uses the X-Forwarded-Host header (preferring it over Host)
273
+ * to validate request origins for security. In Managed Runtime (MRT) with a vanity
274
+ * domain, the eCDN automatically sets the X-Forwarded-Host to the vanity domain.
275
+ * React Router handles cases where this header contains multiple comma-separated
276
+ * values by prioritizing the first entry.
277
+ *
278
+ * This middleware ensures that X-Forwarded-Host is always present by falling back
279
+ * to a configured public domain if the header is missing (e.g., local development).
280
+ * By only modifying X-Forwarded-Host, we provide a consistent environment for
281
+ * React Router's security checks without modifying the internal 'Host' header,
282
+ * which is required for environment-specific routing logic (e.g., Hybrid Proxy).
283
+ *
284
+ * Priority order:
285
+ * 1. X-Forwarded-Host: Automatically set by eCDN for vanity domains.
286
+ * 2. EXTERNAL_DOMAIN_NAME: Fallback environment variable for the public domain
287
+ * used when no forwarded headers are present (e.g., local development).
288
+ */
289
+ function createHostHeaderMiddleware() {
290
+ return (req, _res, next) => {
291
+ if (!req.get("x-forwarded-host") && process.env.EXTERNAL_DOMAIN_NAME) req.headers["x-forwarded-host"] = process.env.EXTERNAL_DOMAIN_NAME;
292
+ next();
293
+ };
294
+ }
295
+
296
+ //#endregion
297
+ //#region src/server/utils.ts
298
+ /**
299
+ * Patch React Router build to rewrite asset URLs with the correct bundle path
300
+ * This is needed because the build output uses /assets/ but we preview at /mobify/bundle/{BUNDLE_ID}/client/assets/
301
+ */
302
+ function patchReactRouterBuild(build, bundleId) {
303
+ const bundlePath = getBundlePath(bundleId);
304
+ const patchedAssetsJson = JSON.stringify(build.assets).replace(/"\/assets\//g, `"${bundlePath}assets/`);
305
+ const newAssets = JSON.parse(patchedAssetsJson);
306
+ return Object.assign({}, build, {
307
+ publicPath: bundlePath,
308
+ assets: newAssets
309
+ });
310
+ }
311
+
312
+ //#endregion
313
+ //#region src/server/modes.ts
314
+ /**
315
+ * Default feature configuration for each server mode
316
+ */
317
+ const ServerModeFeatureMap = {
318
+ development: {
319
+ enableProxy: true,
320
+ enableStaticServing: false,
321
+ enableCompression: false,
322
+ enableLogging: true,
323
+ enableAssetUrlPatching: false
324
+ },
325
+ preview: {
326
+ enableProxy: true,
327
+ enableStaticServing: true,
328
+ enableCompression: true,
329
+ enableLogging: true,
330
+ enableAssetUrlPatching: true
331
+ },
332
+ production: {
333
+ enableProxy: false,
334
+ enableStaticServing: false,
335
+ enableCompression: true,
336
+ enableLogging: true,
337
+ enableAssetUrlPatching: true
338
+ }
339
+ };
340
+
341
+ //#endregion
342
+ //#region src/server/index.ts
343
+ /** Relative path to the middleware registry TypeScript source (development). Must match appDirectory + server dir + filename used by buildMiddlewareRegistry plugin. */
344
+ const RELATIVE_MIDDLEWARE_REGISTRY_SOURCE = "src/server/middleware-registry.ts";
345
+ /** Extensions to try for the built middlewares module (ESM first, then CJS for backwards compatibility). */
346
+ const MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS = [
347
+ ".mjs",
348
+ ".js",
349
+ ".cjs"
350
+ ];
351
+ /** All paths to try when loading the built middlewares (base + extension). */
352
+ const RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS = ["bld/server/middleware-registry", "build/server/middleware-registry"].flatMap((base) => MIDDLEWARE_REGISTRY_BUILT_EXTENSIONS.map((ext) => `${base}${ext}`));
353
+ /**
354
+ * Create a unified Express server for development, preview, or production mode
355
+ */
356
+ async function createServer(options) {
357
+ const { mode, projectDirectory = process.cwd(), config: providedConfig, vite, build, streaming = false, enableProxy = ServerModeFeatureMap[mode].enableProxy, enableStaticServing = ServerModeFeatureMap[mode].enableStaticServing, enableCompression = ServerModeFeatureMap[mode].enableCompression, enableLogging = ServerModeFeatureMap[mode].enableLogging, enableAssetUrlPatching = ServerModeFeatureMap[mode].enableAssetUrlPatching } = options;
358
+ if (mode === "development" && !vite) throw new Error("Vite dev server instance is required for development mode");
359
+ if ((mode === "preview" || mode === "production") && !build) throw new Error("React Router server build is required for preview/production mode");
360
+ const config = providedConfig ?? loadConfigFromEnv();
361
+ const bundleId = process.env.BUNDLE_ID ?? "local";
362
+ const app = express();
363
+ app.disable("x-powered-by");
364
+ if (enableLogging) app.use(createLoggingMiddleware());
365
+ if (enableCompression && !streaming) app.use(createCompressionMiddleware());
366
+ if (enableStaticServing && build) {
367
+ const bundlePath = getBundlePath(bundleId);
368
+ app.use(bundlePath, createStaticMiddleware(bundleId, projectDirectory));
369
+ }
370
+ let registry = null;
371
+ if (mode === "development") {
372
+ const middlewareRegistryPath = resolve(projectDirectory, RELATIVE_MIDDLEWARE_REGISTRY_SOURCE);
373
+ if (existsSync(middlewareRegistryPath)) registry = await importTypescript(middlewareRegistryPath, { projectDirectory });
374
+ } else {
375
+ const possiblePaths = RELATIVE_MIDDLEWARE_REGISTRY_BUILT_PATHS.map((p) => resolve(projectDirectory, p));
376
+ let builtRegistryPath = null;
377
+ for (const path$1 of possiblePaths) if (existsSync(path$1)) {
378
+ builtRegistryPath = path$1;
379
+ break;
380
+ }
381
+ if (builtRegistryPath) registry = await import(pathToFileURL(builtRegistryPath).href);
382
+ }
383
+ if (registry?.customMiddlewares && Array.isArray(registry.customMiddlewares)) registry.customMiddlewares.forEach((entry) => {
384
+ app.use(entry.handler);
385
+ });
386
+ if (mode === "development" && vite) app.use(vite.middlewares);
387
+ if (enableProxy) app.use(config.commerce.api.proxy, createCommerceProxyMiddleware(config));
388
+ app.use(createHostHeaderMiddleware());
389
+ app.all("*", await createSSRHandler(mode, bundleId, vite, build, enableAssetUrlPatching));
390
+ return app;
391
+ }
392
+ /**
393
+ * Create the SSR request handler based on mode
394
+ */
395
+ async function createSSRHandler(mode, bundleId, vite, build, enableAssetUrlPatching) {
396
+ if (mode === "development" && vite) {
397
+ const { isRunnableDevEnvironment } = await import("vite");
398
+ return async (req, res, next) => {
399
+ try {
400
+ const ssrEnvironment = vite.environments.ssr;
401
+ if (!isRunnableDevEnvironment(ssrEnvironment)) {
402
+ next(/* @__PURE__ */ new Error("SSR environment is not runnable. Please ensure:\n 1. \"@salesforce/storefront-next-dev\" plugin is added to vite.config.ts\n 2. React Router config uses the Storefront Next preset"));
403
+ return;
404
+ }
405
+ await createRequestHandler({
406
+ build: await ssrEnvironment.runner.import("virtual:react-router/server-build"),
407
+ mode: process.env.NODE_ENV
408
+ })(req, res, next);
409
+ } catch (error) {
410
+ vite.ssrFixStacktrace(error);
411
+ next(error);
412
+ }
413
+ };
414
+ } else if (build) {
415
+ let patchedBuild = build;
416
+ if (enableAssetUrlPatching) patchedBuild = patchReactRouterBuild(build, bundleId);
417
+ return createRequestHandler({
418
+ build: patchedBuild,
419
+ mode: process.env.NODE_ENV
420
+ });
421
+ } else throw new Error("Invalid server configuration: no vite or build provided");
422
+ }
423
+
424
+ //#endregion
425
+ export { getCommerceCloudApiUrl as n, loadProjectConfig as r, createServer as t };
package/dist/utils.js ADDED
@@ -0,0 +1,126 @@
1
+ import { c as warn, t as debug } from "./logger.js";
2
+ import { execSync } from "child_process";
3
+ import os from "os";
4
+ import path from "path";
5
+ import fs from "fs-extra";
6
+ import dotenv from "dotenv";
7
+
8
+ //#region src/utils.ts
9
+ const getDefaultBuildDir = (targetDir) => path.join(targetDir, "build");
10
+ const NODE_ENV = process.env.NODE_ENV || "development";
11
+ /**
12
+ * Get project package.json
13
+ */
14
+ const getProjectPkg = (projectDir) => {
15
+ const packagePath = path.join(projectDir, "package.json");
16
+ try {
17
+ return fs.readJSONSync(packagePath);
18
+ } catch {
19
+ throw new Error(`Could not read project package at "${packagePath}"`);
20
+ }
21
+ };
22
+ /**
23
+ * Load .env file from project directory
24
+ */
25
+ const loadEnvFile = (projectDir) => {
26
+ const envPath = path.join(projectDir, ".env");
27
+ if (fs.existsSync(envPath)) dotenv.config({ path: envPath });
28
+ else warn("No .env file found");
29
+ };
30
+ /**
31
+ * Get MRT configuration with priority logic: .env -> package.json -> defaults
32
+ */
33
+ const getMrtConfig = (projectDir) => {
34
+ loadEnvFile(projectDir);
35
+ const pkg = getProjectPkg(projectDir);
36
+ const defaultMrtProject = process.env.MRT_PROJECT ?? pkg.name;
37
+ if (!defaultMrtProject || defaultMrtProject.trim() === "") throw new Error("Project name couldn't be determined. Do one of these options:\n 1. Set MRT_PROJECT in your .env file, or\n 2. Ensure package.json has a valid \"name\" field.");
38
+ const defaultMrtTarget = process.env.MRT_TARGET ?? void 0;
39
+ debug("MRT configuration resolved", {
40
+ projectDir,
41
+ envMrtProject: process.env.MRT_PROJECT,
42
+ envMrtTarget: process.env.MRT_TARGET,
43
+ packageName: pkg.name,
44
+ resolvedProject: defaultMrtProject,
45
+ resolvedTarget: defaultMrtTarget
46
+ });
47
+ return {
48
+ defaultMrtProject,
49
+ defaultMrtTarget
50
+ };
51
+ };
52
+ /**
53
+ * Get project dependency tree (simplified version)
54
+ */
55
+ const getProjectDependencyTree = (projectDir) => {
56
+ try {
57
+ const tmpFile = path.join(os.tmpdir(), `npm-ls-${Date.now()}.json`);
58
+ execSync(`npm ls --all --json > ${tmpFile}`, {
59
+ stdio: "ignore",
60
+ cwd: projectDir
61
+ });
62
+ const data = fs.readJSONSync(tmpFile);
63
+ fs.unlinkSync(tmpFile);
64
+ return data;
65
+ } catch {
66
+ return null;
67
+ }
68
+ };
69
+ /**
70
+ * Get PWA Kit dependencies from dependency tree
71
+ */
72
+ const getPwaKitDependencies = (dependencyTree) => {
73
+ if (!dependencyTree) return {};
74
+ const pwaKitDependencies = ["@salesforce/storefront-next-dev"];
75
+ const result = {};
76
+ const searchDeps = (tree) => {
77
+ if (tree.dependencies) for (const [name, dep] of Object.entries(tree.dependencies)) {
78
+ if (pwaKitDependencies.includes(name)) result[name] = dep.version || "unknown";
79
+ if (dep.dependencies) searchDeps({ dependencies: dep.dependencies });
80
+ }
81
+ };
82
+ searchDeps(dependencyTree);
83
+ return result;
84
+ };
85
+ /**
86
+ * Get default commit message from git
87
+ */
88
+ const getDefaultMessage = (projectDir) => {
89
+ try {
90
+ return `${execSync("git rev-parse --abbrev-ref HEAD", {
91
+ encoding: "utf8",
92
+ cwd: projectDir
93
+ }).trim()}: ${execSync("git rev-parse --short HEAD", {
94
+ encoding: "utf8",
95
+ cwd: projectDir
96
+ }).trim()}`;
97
+ } catch {
98
+ debug("Using default bundle message as no message was provided and not in a Git repo.");
99
+ return "PWA Kit Bundle";
100
+ }
101
+ };
102
+ /**
103
+ * Given a project directory and a record of config overrides, generate a new .env file with the overrides based on the .env.default file.
104
+ * @param projectDir
105
+ * @param configOverrides
106
+ */
107
+ const generateEnvFile = (projectDir, configOverrides) => {
108
+ const envDefaultPath = path.join(projectDir, ".env.default");
109
+ const envPath = path.join(projectDir, ".env");
110
+ if (!fs.existsSync(envDefaultPath)) {
111
+ console.warn(`${envDefaultPath} not found`);
112
+ return;
113
+ }
114
+ const envOutputLines = fs.readFileSync(envDefaultPath, "utf8").split("\n").map((line) => {
115
+ if (!line || line.trim().startsWith("#")) return line;
116
+ const eqIndex = line.indexOf("=");
117
+ if (eqIndex === -1) return line;
118
+ const key = line.slice(0, eqIndex);
119
+ const originalValue = line.slice(eqIndex + 1);
120
+ return `${key}=${(Object.prototype.hasOwnProperty.call(configOverrides, key) ? configOverrides[key] : void 0) ?? originalValue}`;
121
+ });
122
+ fs.writeFileSync(envPath, envOutputLines.join("\n"));
123
+ };
124
+
125
+ //#endregion
126
+ export { getProjectDependencyTree as a, loadEnvFile as c, getMrtConfig as i, getDefaultBuildDir as n, getProjectPkg as o, getDefaultMessage as r, getPwaKitDependencies as s, generateEnvFile as t };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/storefront-next-dev",
3
- "version": "0.1.1",
3
+ "version": "0.2.0-alpha.0",
4
4
  "description": "Dev and build tools for SFCC Storefront Next",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -42,10 +42,41 @@
42
42
  "types": "./dist/cartridge-services/index.d.ts",
43
43
  "default": "./dist/cartridge-services/index.js"
44
44
  }
45
+ },
46
+ "./entry/server": {
47
+ "import": {
48
+ "types": "./dist/entry/server.d.ts",
49
+ "default": "./dist/entry/server.js"
50
+ }
51
+ },
52
+ "./entry/client": {
53
+ "import": {
54
+ "types": "./dist/entry/client.d.ts",
55
+ "default": "./dist/entry/client.js"
56
+ }
45
57
  }
46
58
  },
47
59
  "bin": {
48
- "sfnext": "./dist/cli.js"
60
+ "sfnext": "./bin/run.js"
61
+ },
62
+ "oclif": {
63
+ "bin": "sfnext",
64
+ "dirname": "sfnext",
65
+ "commands": "./dist/commands",
66
+ "topicSeparator": " ",
67
+ "hooks": {
68
+ "init": "./dist/hooks/init"
69
+ },
70
+ "plugins": [
71
+ "@oclif/plugin-help",
72
+ "@oclif/plugin-plugins",
73
+ "@oclif/plugin-not-found"
74
+ ],
75
+ "topics": {
76
+ "extensions": {
77
+ "description": "Manage storefront extensions"
78
+ }
79
+ }
49
80
  },
50
81
  "files": [
51
82
  "dist"
@@ -70,6 +101,7 @@
70
101
  "vite": ">=7.0.0"
71
102
  },
72
103
  "devDependencies": {
104
+ "@oclif/test": "^4.1.15",
73
105
  "@react-router/dev": "7.12.0",
74
106
  "@serverless/event-mocks": "1.1.1",
75
107
  "@types/archiver": "^6.0.2",
@@ -81,7 +113,6 @@
81
113
  "@types/express": "4.17.23",
82
114
  "@types/fs-extra": "^11.0.4",
83
115
  "@types/glob": "^8.1.0",
84
- "@types/handlebars": "^4.1.0",
85
116
  "@types/jest": "30.0.0",
86
117
  "@types/minimatch": "^5.1.2",
87
118
  "@types/morgan": "^1.9.9",
@@ -107,16 +138,20 @@
107
138
  },
108
139
  "dependencies": {
109
140
  "tsdown": "^0.15.4",
141
+ "@babel/generator": "7.28.5",
110
142
  "@babel/parser": "^7.28.4",
111
143
  "@babel/traverse": "^7.28.4",
112
144
  "@babel/types": "7.28.4",
113
- "@babel/generator": "7.28.5",
114
145
  "@codegenie/serverless-express": "4.17.0",
115
146
  "@h4ad/serverless-adapter": "4.4.0",
147
+ "@oclif/core": "^4.2.10",
148
+ "@oclif/plugin-help": "^6.2.36",
149
+ "@oclif/plugin-not-found": "^3.2.73",
150
+ "@oclif/plugin-plugins": "^5",
116
151
  "@react-router/express": "7.12.0",
152
+ "@salesforce/b2c-tooling-sdk": "^0.5.4",
117
153
  "archiver": "^7.0.1",
118
154
  "chalk": "^5.3.0",
119
- "commander": "^12.1.0",
120
155
  "compressible": "2.0.18",
121
156
  "compression": "^1.7.4",
122
157
  "dotenv": "^16.4.7",
@@ -150,9 +185,9 @@
150
185
  "lint": "eslint src --ext .ts,.tsx",
151
186
  "lint:fix": "eslint src --ext .ts,.tsx --fix",
152
187
  "clean": "shx rm -rf dist",
153
- "push": "node dist/cli.js push",
154
- "create-bundle": "node dist/cli.js create-bundle",
155
- "generate-cartridge": "node dist/cli.js generate-cartridge",
156
- "deploy-cartridge": "node dist/cli.js deploy-cartridge"
188
+ "push": "./bin/run.js push",
189
+ "create-bundle": "./bin/run.js create-bundle",
190
+ "generate-cartridge": "./bin/run.js generate-cartridge",
191
+ "deploy-cartridge": "./bin/run.js deploy-cartridge"
157
192
  }
158
193
  }