@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.
- package/README.md +45 -36
- package/bin/run.js +12 -0
- package/dist/bundle.js +83 -0
- package/dist/cartridge-services/index.d.ts +2 -26
- package/dist/cartridge-services/index.d.ts.map +1 -1
- package/dist/cartridge-services/index.js +3 -336
- package/dist/cartridge-services/index.js.map +1 -1
- package/dist/commands/create-bundle.js +107 -0
- package/dist/commands/create-instructions.js +174 -0
- package/dist/commands/create-storefront.js +210 -0
- package/dist/commands/deploy-cartridge.js +52 -0
- package/dist/commands/dev.js +122 -0
- package/dist/commands/extensions/create.js +38 -0
- package/dist/commands/extensions/install.js +44 -0
- package/dist/commands/extensions/list.js +21 -0
- package/dist/commands/extensions/remove.js +38 -0
- package/dist/commands/generate-cartridge.js +35 -0
- package/dist/commands/prepare-local.js +30 -0
- package/dist/commands/preview.js +101 -0
- package/dist/commands/push.js +139 -0
- package/dist/config.js +87 -0
- package/dist/configs/react-router.config.js +3 -1
- package/dist/configs/react-router.config.js.map +1 -1
- package/dist/dependency-utils.js +314 -0
- package/dist/entry/client.d.ts +1 -0
- package/dist/entry/client.js +28 -0
- package/dist/entry/client.js.map +1 -0
- package/dist/entry/server.d.ts +15 -0
- package/dist/entry/server.d.ts.map +1 -0
- package/dist/entry/server.js +35 -0
- package/dist/entry/server.js.map +1 -0
- package/dist/flags.js +11 -0
- package/dist/generate-cartridge.js +620 -0
- package/dist/hooks/init.js +47 -0
- package/dist/index.d.ts +9 -29
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +413 -621
- package/dist/index.js.map +1 -1
- package/dist/local-dev-setup.js +176 -0
- package/dist/logger.js +105 -0
- package/dist/manage-extensions.js +329 -0
- package/dist/mrt/ssr.mjs +3 -3
- package/dist/mrt/ssr.mjs.map +1 -1
- package/dist/mrt/streamingHandler.mjs +4 -4
- package/dist/mrt/streamingHandler.mjs.map +1 -1
- package/dist/server.js +425 -0
- package/dist/utils.js +126 -0
- package/package.json +44 -9
- package/dist/cli.js +0 -3393
- /package/{LICENSE.txt → LICENSE} +0 -0
package/dist/index.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import path, { basename, extname, join, resolve } from "node:path";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import path$1, { dirname, join as join$1, relative, resolve as resolve$1 } from "path";
|
|
4
|
-
import {
|
|
4
|
+
import { fileURLToPath, pathToFileURL } from "url";
|
|
5
5
|
import { parse } from "@babel/parser";
|
|
6
6
|
import { isJSXAttribute, isJSXElement, isJSXFragment, isJSXIdentifier, jsxClosingElement, jsxClosingFragment, jsxElement, jsxFragment, jsxIdentifier, jsxOpeningElement, jsxOpeningFragment, jsxText } from "@babel/types";
|
|
7
7
|
import { generate } from "@babel/generator";
|
|
@@ -9,22 +9,18 @@ import traverseModule from "@babel/traverse";
|
|
|
9
9
|
import fs$1, { existsSync, readFileSync, writeFileSync } from "fs";
|
|
10
10
|
import { glob } from "glob";
|
|
11
11
|
import { Node, Project, ts } from "ts-morph";
|
|
12
|
-
import
|
|
13
|
-
import archiver from "archiver";
|
|
14
|
-
import { Minimatch, minimatch } from "minimatch";
|
|
15
|
-
import { execSync } from "child_process";
|
|
16
|
-
import dotenv from "dotenv";
|
|
17
|
-
import chalk from "chalk";
|
|
12
|
+
import fs$2, { existsSync as existsSync$1, readFileSync as readFileSync$1, unlinkSync } from "node:fs";
|
|
18
13
|
import express from "express";
|
|
19
14
|
import { createRequestHandler } from "@react-router/express";
|
|
20
|
-
import { existsSync as existsSync$1, readFileSync as readFileSync$1, unlinkSync } from "node:fs";
|
|
21
15
|
import { pathToFileURL as pathToFileURL$1 } from "node:url";
|
|
22
16
|
import { createProxyMiddleware } from "http-proxy-middleware";
|
|
17
|
+
import chalk from "chalk";
|
|
23
18
|
import compression from "compression";
|
|
24
19
|
import zlib from "node:zlib";
|
|
25
20
|
import morgan from "morgan";
|
|
21
|
+
import { minimatch } from "minimatch";
|
|
26
22
|
import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
27
|
-
import { execSync
|
|
23
|
+
import { execSync } from "node:child_process";
|
|
28
24
|
import { tmpdir } from "node:os";
|
|
29
25
|
import { randomUUID } from "node:crypto";
|
|
30
26
|
import { npmRunPathEnv } from "npm-run-path";
|
|
@@ -62,6 +58,44 @@ function fixReactRouterManifestUrlsPlugin() {
|
|
|
62
58
|
};
|
|
63
59
|
}
|
|
64
60
|
|
|
61
|
+
//#endregion
|
|
62
|
+
//#region src/utils/paths.ts
|
|
63
|
+
/**
|
|
64
|
+
* Copyright 2026 Salesforce, Inc.
|
|
65
|
+
*
|
|
66
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
67
|
+
* you may not use this file except in compliance with the License.
|
|
68
|
+
* You may obtain a copy of the License at
|
|
69
|
+
*
|
|
70
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
71
|
+
*
|
|
72
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
73
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
74
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
75
|
+
* See the License for the specific language governing permissions and
|
|
76
|
+
* limitations under the License.
|
|
77
|
+
*/
|
|
78
|
+
/**
|
|
79
|
+
* Normalize a file path to use forward slashes.
|
|
80
|
+
* On Windows, Node APIs return backslash-separated paths, but ESM import
|
|
81
|
+
* specifiers and Vite module IDs require forward slashes.
|
|
82
|
+
*/
|
|
83
|
+
function toPosixPath(filePath) {
|
|
84
|
+
return filePath.replace(/\\/g, "/");
|
|
85
|
+
}
|
|
86
|
+
/**
|
|
87
|
+
* Get the Commerce Cloud API URL from a short code
|
|
88
|
+
*/
|
|
89
|
+
function getCommerceCloudApiUrl(shortCode, proxyHost) {
|
|
90
|
+
return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;
|
|
91
|
+
}
|
|
92
|
+
/**
|
|
93
|
+
* Get the bundle path for static assets
|
|
94
|
+
*/
|
|
95
|
+
function getBundlePath(bundleId) {
|
|
96
|
+
return `/mobify/bundle/${bundleId}/client/`;
|
|
97
|
+
}
|
|
98
|
+
|
|
65
99
|
//#endregion
|
|
66
100
|
//#region src/plugins/readableChunkFileNames.ts
|
|
67
101
|
/**
|
|
@@ -89,9 +123,6 @@ const readableChunkFileNames = (chunkInfo) => {
|
|
|
89
123
|
const defaultName = "assets/(chunk)-[name].[hash].js";
|
|
90
124
|
if (!moduleIds || moduleIds.length === 0) return defaultName;
|
|
91
125
|
const lastModuleId = moduleIds[moduleIds.length - 1];
|
|
92
|
-
const toPosixPath = (pathname) => {
|
|
93
|
-
return pathname.replace(/\\/g, "/");
|
|
94
|
-
};
|
|
95
126
|
const getFileName = (pathname) => {
|
|
96
127
|
const posixPath = toPosixPath(pathname);
|
|
97
128
|
return path$1.posix.parse(posixPath).base.split("?")[0].replace(/\.(tsx?|jsx?|mjs|js)$/, "");
|
|
@@ -279,18 +310,18 @@ const patchReactRouterPlugin = () => {
|
|
|
279
310
|
};
|
|
280
311
|
|
|
281
312
|
//#endregion
|
|
282
|
-
//#region src/extensibility/
|
|
313
|
+
//#region src/extensibility/target-utils.ts
|
|
283
314
|
const traverse = traverseModule.default || traverseModule;
|
|
284
|
-
const
|
|
285
|
-
const
|
|
286
|
-
const
|
|
315
|
+
const TARGET_COMPONENT_TAG = "UITarget";
|
|
316
|
+
const TARGET_PROVIDERS_TAG = "TargetProviders";
|
|
317
|
+
const TARGET_ID_ATTRIBUTE = "targetId";
|
|
287
318
|
/**
|
|
288
|
-
* Find and replace the
|
|
319
|
+
* Find and replace the TargetProviders tags with the corresponding context providers
|
|
289
320
|
* @param element - the AST element to replace
|
|
290
321
|
* @param contextProviders - the context providers to replace
|
|
291
322
|
*/
|
|
292
323
|
function findAndReplaceProviders(element, contextProviders) {
|
|
293
|
-
if (isJSXIdentifier(element.node.openingElement.name, { name:
|
|
324
|
+
if (isJSXIdentifier(element.node.openingElement.name, { name: TARGET_PROVIDERS_TAG })) if (contextProviders.length > 0) {
|
|
294
325
|
let nested = element.node.children;
|
|
295
326
|
for (let i = contextProviders.length - 1; i >= 0; i--) {
|
|
296
327
|
const componentName = contextProviders[i].componentName;
|
|
@@ -300,49 +331,49 @@ function findAndReplaceProviders(element, contextProviders) {
|
|
|
300
331
|
} else element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), element.node.children));
|
|
301
332
|
}
|
|
302
333
|
/**
|
|
303
|
-
* Find and replace the
|
|
334
|
+
* Find and replace the target component with the replacement code
|
|
304
335
|
* @param componentName - the name of the component to replace
|
|
305
336
|
* @param element - the AST element as the replacement candidate
|
|
306
|
-
* @param
|
|
307
|
-
* @returns the
|
|
337
|
+
* @param targetRegistry - the target registry
|
|
338
|
+
* @returns the targetId that was replaced, or null if no replacement was found
|
|
308
339
|
*/
|
|
309
|
-
function findAndReplaceComponent(componentName, element,
|
|
310
|
-
let
|
|
340
|
+
function findAndReplaceComponent(componentName, element, targetRegistry) {
|
|
341
|
+
let targetIdReplaced = null;
|
|
311
342
|
if (isJSXIdentifier(element.node.openingElement.name, { name: componentName })) {
|
|
312
343
|
let replaced = false;
|
|
313
344
|
if (Array.isArray(element.node.openingElement.attributes)) {
|
|
314
|
-
const attr = element.node.openingElement.attributes.find((a) => isJSXAttribute(a) && isJSXIdentifier(a.name, { name:
|
|
315
|
-
const
|
|
316
|
-
if (
|
|
317
|
-
if (
|
|
318
|
-
const components =
|
|
319
|
-
return jsxElement(jsxOpeningElement(jsxIdentifier(
|
|
345
|
+
const attr = element.node.openingElement.attributes.find((a) => isJSXAttribute(a) && isJSXIdentifier(a.name, { name: TARGET_ID_ATTRIBUTE }));
|
|
346
|
+
const targetId = attr && isJSXAttribute(attr) && attr.value && "value" in attr.value ? attr.value.value : void 0;
|
|
347
|
+
if (targetId == null) throw new Error(`UITarget must contain a targetId attribute`);
|
|
348
|
+
if (targetRegistry[targetId] && targetRegistry[targetId].length > 0) {
|
|
349
|
+
const components = targetRegistry[targetId].map((targetComponent) => {
|
|
350
|
+
return jsxElement(jsxOpeningElement(jsxIdentifier(targetComponent.componentName), [], true), null, [], true);
|
|
320
351
|
});
|
|
321
352
|
if (components.length > 1) element.replaceWith(jsxFragment(jsxOpeningFragment(), jsxClosingFragment(), components));
|
|
322
353
|
else element.replaceWith(components[0]);
|
|
323
|
-
|
|
354
|
+
targetIdReplaced = targetId;
|
|
324
355
|
replaced = true;
|
|
325
356
|
}
|
|
326
357
|
}
|
|
327
358
|
if (!replaced) if (element.node.children && element.node.children.length > 0) element.replaceWithMultiple(element.node.children);
|
|
328
359
|
else element.remove();
|
|
329
360
|
}
|
|
330
|
-
return
|
|
361
|
+
return targetIdReplaced;
|
|
331
362
|
}
|
|
332
363
|
/**
|
|
333
364
|
* Run a replacement pass on the AST
|
|
334
365
|
* @param ast - the AST to traverse
|
|
335
366
|
* @param tagName - the name of the tag to replace
|
|
336
|
-
* @param
|
|
367
|
+
* @param targetRegistry - the target registry
|
|
337
368
|
* @param contextProviders - the context providers to replace
|
|
338
|
-
* @returns a set of
|
|
369
|
+
* @returns a set of targetIds that were replaced
|
|
339
370
|
*/
|
|
340
|
-
function runReplacementPass(ast, tagName,
|
|
341
|
-
const
|
|
371
|
+
function runReplacementPass(ast, tagName, targetRegistry = null, contextProviders = null) {
|
|
372
|
+
const targetIdsReplaced = /* @__PURE__ */ new Set();
|
|
342
373
|
const applyReplacement = (pathToReplace) => {
|
|
343
|
-
if (
|
|
344
|
-
const replacedId = findAndReplaceComponent(tagName, pathToReplace,
|
|
345
|
-
if (replacedId)
|
|
374
|
+
if (targetRegistry) {
|
|
375
|
+
const replacedId = findAndReplaceComponent(tagName, pathToReplace, targetRegistry);
|
|
376
|
+
if (replacedId) targetIdsReplaced.add(replacedId);
|
|
346
377
|
} else if (contextProviders) findAndReplaceProviders(pathToReplace, contextProviders);
|
|
347
378
|
};
|
|
348
379
|
traverse(ast, {
|
|
@@ -370,24 +401,24 @@ function runReplacementPass(ast, tagName, pluginRegistry = null, contextProvider
|
|
|
370
401
|
} });
|
|
371
402
|
}
|
|
372
403
|
});
|
|
373
|
-
return
|
|
404
|
+
return targetIdsReplaced;
|
|
374
405
|
}
|
|
375
406
|
/**
|
|
376
|
-
* Build the import statements for the
|
|
377
|
-
* @param
|
|
378
|
-
* @param
|
|
407
|
+
* Build the import statements for the target components
|
|
408
|
+
* @param targetIds - the targetIds that were replaced
|
|
409
|
+
* @param targetRegistry - the target registry
|
|
379
410
|
* @returns the import statements
|
|
380
411
|
*/
|
|
381
|
-
function buildReplacementImportStatements(
|
|
412
|
+
function buildReplacementImportStatements(targetIds, targetRegistry) {
|
|
382
413
|
const importStatements = /* @__PURE__ */ new Set();
|
|
383
|
-
for (const
|
|
384
|
-
const
|
|
385
|
-
for (const
|
|
414
|
+
for (const targetId of targetIds) {
|
|
415
|
+
const targetComponents = targetRegistry[targetId];
|
|
416
|
+
for (const targetComponent of targetComponents) importStatements.add(`import ${targetComponent.componentName} from '@/${targetComponent.path.replace(".tsx", "")}';`);
|
|
386
417
|
}
|
|
387
418
|
return Array.from(importStatements).join("\n");
|
|
388
419
|
}
|
|
389
|
-
function
|
|
390
|
-
if (!code.includes(
|
|
420
|
+
function transformTargets(code, targetRegistry, contextProviders) {
|
|
421
|
+
if (!code.includes(TARGET_COMPONENT_TAG) && !code.includes(TARGET_PROVIDERS_TAG)) return null;
|
|
391
422
|
const ast = parse(code, {
|
|
392
423
|
sourceType: "module",
|
|
393
424
|
plugins: [
|
|
@@ -396,30 +427,30 @@ function transformPlugins(code, pluginRegistry, contextProviders) {
|
|
|
396
427
|
"decorators-legacy"
|
|
397
428
|
]
|
|
398
429
|
});
|
|
399
|
-
if (code.includes(
|
|
400
|
-
const replacementImportStatements = buildReplacementImportStatements(runReplacementPass(ast,
|
|
430
|
+
if (code.includes(TARGET_COMPONENT_TAG)) {
|
|
431
|
+
const replacementImportStatements = buildReplacementImportStatements(runReplacementPass(ast, TARGET_COMPONENT_TAG, targetRegistry, null), targetRegistry);
|
|
401
432
|
traverse(ast, { ImportDeclaration(nodePath) {
|
|
402
|
-
if (nodePath.node.source.value.includes("@/
|
|
433
|
+
if (nodePath.node.source.value.includes("@/targets/ui-target")) nodePath.replaceWith(jsxText(replacementImportStatements));
|
|
403
434
|
} });
|
|
404
435
|
}
|
|
405
|
-
if (code.includes(
|
|
436
|
+
if (code.includes(TARGET_PROVIDERS_TAG)) {
|
|
406
437
|
const importStatements = /* @__PURE__ */ new Set();
|
|
407
438
|
for (const contextProvider of contextProviders) importStatements.add(`import ${contextProvider.componentName} from '@/${contextProvider.path.replace(".tsx", "")}';`);
|
|
408
439
|
const replacementImportStatements = Array.from(importStatements).join("\n");
|
|
409
440
|
traverse(ast, { ImportDeclaration(nodePath) {
|
|
410
|
-
if (nodePath.node.source.value.includes("@/
|
|
441
|
+
if (nodePath.node.source.value.includes("@/targets/target-providers")) nodePath.replaceWith(jsxText(replacementImportStatements));
|
|
411
442
|
} });
|
|
412
|
-
runReplacementPass(ast,
|
|
443
|
+
runReplacementPass(ast, TARGET_PROVIDERS_TAG, null, contextProviders);
|
|
413
444
|
}
|
|
414
445
|
return generate(ast).code;
|
|
415
446
|
}
|
|
416
447
|
/**
|
|
417
|
-
* Build the
|
|
448
|
+
* Build the target registry from the extension directories
|
|
418
449
|
* @param rootDir - the root directory of the project
|
|
419
450
|
* @param sourceDir - the source directory of the project
|
|
420
|
-
* @returns the
|
|
451
|
+
* @returns the target registry
|
|
421
452
|
*/
|
|
422
|
-
function
|
|
453
|
+
function buildTargetRegistry(rootDir) {
|
|
423
454
|
const componentRegistry = {};
|
|
424
455
|
const contextProviders = [];
|
|
425
456
|
const extensionDirPath = path$1.join(rootDir, "extensions");
|
|
@@ -431,17 +462,18 @@ function buildPluginRegistry(rootDir) {
|
|
|
431
462
|
componentName: `${namespace}_${(filePath.split("/").pop()?.replace(".tsx", ""))?.split("-").map((word) => word.charAt(0).toUpperCase() + word.slice(1).toLowerCase()).join("")}`
|
|
432
463
|
};
|
|
433
464
|
};
|
|
465
|
+
const TARGET_CONFIG_FILENAME = "target-config.json";
|
|
434
466
|
for (const dir of extensionDirs) if (dir.isDirectory()) {
|
|
435
|
-
const configPath = path$1.join(extensionDirPath, dir.name,
|
|
467
|
+
const configPath = path$1.join(extensionDirPath, dir.name, TARGET_CONFIG_FILENAME);
|
|
436
468
|
if (fs.existsSync(configPath)) {
|
|
437
|
-
const
|
|
438
|
-
if (
|
|
439
|
-
const {
|
|
440
|
-
if (
|
|
441
|
-
if (!componentRegistry[
|
|
469
|
+
const extensionConfig = fs.readJsonSync(configPath);
|
|
470
|
+
if (extensionConfig && extensionConfig.components) for (const component of extensionConfig.components) {
|
|
471
|
+
const { targetId, path: componentPath, order = 0 } = component;
|
|
472
|
+
if (targetId && componentPath) {
|
|
473
|
+
if (!componentRegistry[targetId]) componentRegistry[targetId] = [];
|
|
442
474
|
const { namespace, componentName } = getNamespaceAndComponentName(dir, componentPath);
|
|
443
|
-
componentRegistry[
|
|
444
|
-
|
|
475
|
+
componentRegistry[targetId].push({
|
|
476
|
+
targetId,
|
|
445
477
|
path: componentPath,
|
|
446
478
|
order,
|
|
447
479
|
namespace,
|
|
@@ -449,7 +481,7 @@ function buildPluginRegistry(rootDir) {
|
|
|
449
481
|
});
|
|
450
482
|
}
|
|
451
483
|
}
|
|
452
|
-
if (
|
|
484
|
+
if (extensionConfig && extensionConfig.contextProviders) for (const contextProvider of extensionConfig.contextProviders) {
|
|
453
485
|
const { path: providerPath, order = 0 } = contextProvider;
|
|
454
486
|
if (providerPath) {
|
|
455
487
|
const { namespace, componentName } = getNamespaceAndComponentName(dir, providerPath);
|
|
@@ -463,7 +495,7 @@ function buildPluginRegistry(rootDir) {
|
|
|
463
495
|
}
|
|
464
496
|
}
|
|
465
497
|
}
|
|
466
|
-
for (const
|
|
498
|
+
for (const targetId in componentRegistry) componentRegistry[targetId].sort((a, b) => a.order - b.order);
|
|
467
499
|
contextProviders.sort((a, b) => a.order - b.order);
|
|
468
500
|
return {
|
|
469
501
|
componentRegistry,
|
|
@@ -472,30 +504,30 @@ function buildPluginRegistry(rootDir) {
|
|
|
472
504
|
}
|
|
473
505
|
|
|
474
506
|
//#endregion
|
|
475
|
-
//#region src/plugins/
|
|
476
|
-
function
|
|
507
|
+
//#region src/plugins/transformTargets.ts
|
|
508
|
+
function transformTargetPlaceholderPlugin() {
|
|
477
509
|
let componentRegistry;
|
|
478
510
|
let contextProviders;
|
|
479
511
|
let sourceDir;
|
|
480
512
|
return {
|
|
481
|
-
name: "odyssey:transform-
|
|
513
|
+
name: "odyssey:transform-target-placeholder",
|
|
482
514
|
enforce: "pre",
|
|
483
515
|
configResolved(config) {
|
|
484
516
|
sourceDir = config.resolve.alias.find((alias) => alias.find === "@")?.replacement || path$1.resolve(__dirname, "./src");
|
|
485
517
|
},
|
|
486
518
|
buildStart() {
|
|
487
|
-
({componentRegistry, contextProviders} =
|
|
519
|
+
({componentRegistry, contextProviders} = buildTargetRegistry(sourceDir));
|
|
488
520
|
},
|
|
489
521
|
transform(code, id) {
|
|
490
522
|
try {
|
|
491
|
-
const transformedCode =
|
|
523
|
+
const transformedCode = transformTargets(code, componentRegistry, contextProviders);
|
|
492
524
|
if (transformedCode) return {
|
|
493
525
|
code: transformedCode,
|
|
494
526
|
map: null
|
|
495
527
|
};
|
|
496
528
|
return null;
|
|
497
529
|
} catch (err) {
|
|
498
|
-
console.error(`
|
|
530
|
+
console.error(`UITarget replace ERROR in ${id}: ${err instanceof Error ? err.stack : String(err)}`);
|
|
499
531
|
throw err;
|
|
500
532
|
}
|
|
501
533
|
}
|
|
@@ -514,11 +546,11 @@ const watchConfigFilesPlugin = () => {
|
|
|
514
546
|
configureServer(server) {
|
|
515
547
|
const aliases = viteConfig.resolve.alias;
|
|
516
548
|
const root = Object.values(aliases).find((alias) => alias.find === "@")?.replacement || "src";
|
|
517
|
-
const glob$1 = path$1.posix.join(root, "extensions", "**", "
|
|
549
|
+
const glob$1 = path$1.posix.join(root, "extensions", "**", "target-config.json");
|
|
518
550
|
server.watcher.add(glob$1);
|
|
519
551
|
const onChange = (file) => {
|
|
520
|
-
if (file.endsWith("
|
|
521
|
-
console.log(`🔁
|
|
552
|
+
if (file.endsWith("target-config.json")) {
|
|
553
|
+
console.log(`🔁 target-config.json changed: ${file}`);
|
|
522
554
|
server.restart();
|
|
523
555
|
}
|
|
524
556
|
};
|
|
@@ -639,8 +671,8 @@ async function scanComponents(project, projectRoot, componentPath, registryPath,
|
|
|
639
671
|
}
|
|
640
672
|
}
|
|
641
673
|
}
|
|
642
|
-
} catch (error
|
|
643
|
-
if (verbose$1) console.warn(`⚠️ Could not process ${filePath}:`, error
|
|
674
|
+
} catch (error) {
|
|
675
|
+
if (verbose$1) console.warn(`⚠️ Could not process ${filePath}:`, error.message);
|
|
644
676
|
}
|
|
645
677
|
return components;
|
|
646
678
|
}
|
|
@@ -705,8 +737,8 @@ export const registry = new ComponentRegistry();
|
|
|
705
737
|
existingContent = basicRegistryContent;
|
|
706
738
|
} else try {
|
|
707
739
|
existingContent = readFileSync(registryFilePath, "utf-8");
|
|
708
|
-
} catch (error
|
|
709
|
-
throw new Error(`Failed to read registry file: ${error
|
|
740
|
+
} catch (error) {
|
|
741
|
+
throw new Error(`Failed to read registry file: ${error.message}`);
|
|
710
742
|
}
|
|
711
743
|
const startMarker = "// STATIC_REGISTRY_START";
|
|
712
744
|
const endMarker = "// STATIC_REGISTRY_END";
|
|
@@ -717,8 +749,8 @@ export const registry = new ComponentRegistry();
|
|
|
717
749
|
try {
|
|
718
750
|
writeFileSync(registryFilePath, updatedContent, "utf-8");
|
|
719
751
|
if (verbose$1) console.log(`💾 Updated registry file: ${registryFilePath}`);
|
|
720
|
-
} catch (error
|
|
721
|
-
throw new Error(`Failed to write registry file: ${error
|
|
752
|
+
} catch (error) {
|
|
753
|
+
throw new Error(`Failed to write registry file: ${error.message}`);
|
|
722
754
|
}
|
|
723
755
|
}
|
|
724
756
|
/**
|
|
@@ -772,9 +804,9 @@ const staticRegistryPlugin = (config = {}) => {
|
|
|
772
804
|
async buildStart() {
|
|
773
805
|
try {
|
|
774
806
|
await runRegistryGeneration();
|
|
775
|
-
} catch (error
|
|
776
|
-
console.error(`❌ Static registry generation failed: ${error
|
|
777
|
-
if (failOnError) throw error
|
|
807
|
+
} catch (error) {
|
|
808
|
+
console.error(`❌ Static registry generation failed: ${error.message}`);
|
|
809
|
+
if (failOnError) throw error;
|
|
778
810
|
console.warn("⚠️ Continuing build without static registry...");
|
|
779
811
|
}
|
|
780
812
|
},
|
|
@@ -788,8 +820,8 @@ const staticRegistryPlugin = (config = {}) => {
|
|
|
788
820
|
const registryModule = server.moduleGraph.getModuleById(registryFilePath);
|
|
789
821
|
if (registryModule) await server.reloadModule(registryModule);
|
|
790
822
|
if (verbose$1) console.log("✅ Registry regenerated successfully!");
|
|
791
|
-
} catch (error
|
|
792
|
-
console.error(`❌ Failed to regenerate registry: ${error
|
|
823
|
+
} catch (error) {
|
|
824
|
+
console.error(`❌ Failed to regenerate registry: ${error.message}`);
|
|
793
825
|
}
|
|
794
826
|
return [];
|
|
795
827
|
}
|
|
@@ -813,8 +845,8 @@ async function loadEngagementConfig(projectRoot, configPath, verbose$1) {
|
|
|
813
845
|
return null;
|
|
814
846
|
}
|
|
815
847
|
return engagement;
|
|
816
|
-
} catch (error
|
|
817
|
-
if (verbose$1) console.warn(` ⚠️ Could not load config from ${configPath}: ${error
|
|
848
|
+
} catch (error) {
|
|
849
|
+
if (verbose$1) console.warn(` ⚠️ Could not load config from ${configPath}: ${error.message}`);
|
|
818
850
|
return null;
|
|
819
851
|
}
|
|
820
852
|
}
|
|
@@ -858,8 +890,8 @@ async function scanForInstrumentedEvents(projectRoot, scanPaths, verbose$1) {
|
|
|
858
890
|
trackEventPattern.lastIndex = 0;
|
|
859
891
|
sendViewPagePattern.lastIndex = 0;
|
|
860
892
|
createEventPattern.lastIndex = 0;
|
|
861
|
-
} catch (error
|
|
862
|
-
if (verbose$1) console.warn(` ⚠️ Could not read ${file}: ${error
|
|
893
|
+
} catch (error) {
|
|
894
|
+
if (verbose$1) console.warn(` ⚠️ Could not read ${file}: ${error.message}`);
|
|
863
895
|
}
|
|
864
896
|
}
|
|
865
897
|
return instrumentedEvents;
|
|
@@ -1020,7 +1052,208 @@ const buildMiddlewareRegistryPlugin = () => {
|
|
|
1020
1052
|
};
|
|
1021
1053
|
|
|
1022
1054
|
//#endregion
|
|
1023
|
-
//#region src/
|
|
1055
|
+
//#region src/plugins/platformEntry.ts
|
|
1056
|
+
/**
|
|
1057
|
+
* File extensions to search when detecting ejected entry files.
|
|
1058
|
+
* Matches React Router's `entryExts` in its findEntry function.
|
|
1059
|
+
*/
|
|
1060
|
+
const ENTRY_EXTENSIONS = [
|
|
1061
|
+
".js",
|
|
1062
|
+
".jsx",
|
|
1063
|
+
".ts",
|
|
1064
|
+
".tsx",
|
|
1065
|
+
".mjs",
|
|
1066
|
+
".mts"
|
|
1067
|
+
];
|
|
1068
|
+
/**
|
|
1069
|
+
* Query parameter appended to imports of ejected entry files within the
|
|
1070
|
+
* generated composition code. This creates a distinct module ID so Vite
|
|
1071
|
+
* treats it as a separate module from the one we intercept in `load`,
|
|
1072
|
+
* breaking what would otherwise be a circular import.
|
|
1073
|
+
*
|
|
1074
|
+
* Vite natively handles query parameters on file imports — it strips the
|
|
1075
|
+
* query for filesystem access but keeps it in the module ID for deduplication.
|
|
1076
|
+
*/
|
|
1077
|
+
const PASSTHROUGH_QUERY = "?platform-passthrough";
|
|
1078
|
+
/**
|
|
1079
|
+
* Finds a user-ejected entry file in the app directory.
|
|
1080
|
+
* Returns the absolute path if found, undefined otherwise.
|
|
1081
|
+
*/
|
|
1082
|
+
function findUserEntry(appDirectory, basename$1) {
|
|
1083
|
+
for (const ext of ENTRY_EXTENSIONS) {
|
|
1084
|
+
const filePath = path.resolve(appDirectory, basename$1 + ext);
|
|
1085
|
+
if (fs$2.existsSync(filePath)) return filePath;
|
|
1086
|
+
}
|
|
1087
|
+
}
|
|
1088
|
+
/**
|
|
1089
|
+
* Generates the virtual module code for the composed server entry.
|
|
1090
|
+
*
|
|
1091
|
+
* The generated module imports the app's entry (user-ejected or SDK default),
|
|
1092
|
+
* passes it through composeServerEntry(), and re-exports all ServerEntryModule
|
|
1093
|
+
* fields. This ensures platform features are always applied.
|
|
1094
|
+
*/
|
|
1095
|
+
function generateServerEntryCode(appEntryImportPath) {
|
|
1096
|
+
const importPath = JSON.stringify(toPosixPath(appEntryImportPath));
|
|
1097
|
+
return `
|
|
1098
|
+
import * as _app from ${importPath};
|
|
1099
|
+
import { composeServerEntry } from '@salesforce/storefront-next-dev/entry/server';
|
|
1100
|
+
|
|
1101
|
+
const _composed = composeServerEntry(_app);
|
|
1102
|
+
|
|
1103
|
+
// Forward all named exports from the app entry so that any future
|
|
1104
|
+
// React Router exports are passed through without requiring a plugin update.
|
|
1105
|
+
// Explicit exports below take precedence over star re-exports per ESM spec.
|
|
1106
|
+
export * from ${importPath};
|
|
1107
|
+
|
|
1108
|
+
// Override with composed versions for exports the platform layer enhances.
|
|
1109
|
+
export default _composed.default;
|
|
1110
|
+
export const handleDataRequest = _composed.handleDataRequest;
|
|
1111
|
+
export const handleError = _composed.handleError;
|
|
1112
|
+
export const unstable_instrumentations = _composed.unstable_instrumentations;
|
|
1113
|
+
export const streamTimeout = _composed.streamTimeout;
|
|
1114
|
+
`.trim();
|
|
1115
|
+
}
|
|
1116
|
+
/**
|
|
1117
|
+
* Generates the virtual module code for the composed client entry.
|
|
1118
|
+
*
|
|
1119
|
+
* Imports the platform client setup as a side-effect (runs before the app entry),
|
|
1120
|
+
* then re-exports everything from the app's client entry.
|
|
1121
|
+
*/
|
|
1122
|
+
function generateClientEntryCode(appEntryImportPath) {
|
|
1123
|
+
return `
|
|
1124
|
+
import '@salesforce/storefront-next-dev/entry/client';
|
|
1125
|
+
export * from ${JSON.stringify(toPosixPath(appEntryImportPath))};
|
|
1126
|
+
`.trim();
|
|
1127
|
+
}
|
|
1128
|
+
/**
|
|
1129
|
+
* Vite plugin that composes platform-level features into React Router entry files.
|
|
1130
|
+
*
|
|
1131
|
+
* This plugin uses the `load` hook to replace entry file contents with generated
|
|
1132
|
+
* composition code, while preserving the original file path as the module ID.
|
|
1133
|
+
* This is critical because React Router's post-build manifest lookup uses the
|
|
1134
|
+
* original entry file paths to find built chunks — changing the module ID (via
|
|
1135
|
+
* `resolveId`) would break that lookup.
|
|
1136
|
+
*
|
|
1137
|
+
* The plugin supports two modes:
|
|
1138
|
+
* - **Non-ejected:** No entry files in the app directory. The generated code
|
|
1139
|
+
* imports SDK default entries from `@salesforce/storefront-next-dev/entry/defaults/`.
|
|
1140
|
+
* - **Ejected:** Customer has created their own entry file(s). The generated code
|
|
1141
|
+
* imports the customer's file (with a `?platform-passthrough` query to avoid
|
|
1142
|
+
* circular imports) and wraps it with the platform layer.
|
|
1143
|
+
*
|
|
1144
|
+
* In both cases, the platform composition layer is always present. New platform
|
|
1145
|
+
* features ship via `npm update` by modifying the composition functions, without
|
|
1146
|
+
* changes to the plugin or customer code.
|
|
1147
|
+
*/
|
|
1148
|
+
function platformEntryPlugin() {
|
|
1149
|
+
let isTestMode = false;
|
|
1150
|
+
let serverEntryFilePath;
|
|
1151
|
+
let clientEntryFilePath;
|
|
1152
|
+
let appDirectory;
|
|
1153
|
+
let userServerEntryPath;
|
|
1154
|
+
let userClientEntryPath;
|
|
1155
|
+
return {
|
|
1156
|
+
name: "odyssey:platform-entry",
|
|
1157
|
+
enforce: "pre",
|
|
1158
|
+
config(_config, { mode }) {
|
|
1159
|
+
isTestMode = mode === "test";
|
|
1160
|
+
},
|
|
1161
|
+
configResolved(config) {
|
|
1162
|
+
if (isTestMode) return;
|
|
1163
|
+
const ctx = config.__reactRouterPluginContext;
|
|
1164
|
+
if (!ctx) return;
|
|
1165
|
+
appDirectory = ctx.reactRouterConfig.appDirectory;
|
|
1166
|
+
serverEntryFilePath = ctx.entryServerFilePath;
|
|
1167
|
+
clientEntryFilePath = ctx.entryClientFilePath;
|
|
1168
|
+
userServerEntryPath = findUserEntry(appDirectory, "entry.server");
|
|
1169
|
+
userClientEntryPath = findUserEntry(appDirectory, "entry.client");
|
|
1170
|
+
},
|
|
1171
|
+
load(id) {
|
|
1172
|
+
if (isTestMode || !serverEntryFilePath || !clientEntryFilePath || !appDirectory) return null;
|
|
1173
|
+
if (id.includes(PASSTHROUGH_QUERY)) return null;
|
|
1174
|
+
const idWithoutQuery = id.split("?")[0];
|
|
1175
|
+
if (path.normalize(idWithoutQuery) === path.normalize(serverEntryFilePath)) return generateServerEntryCode(userServerEntryPath ? userServerEntryPath + PASSTHROUGH_QUERY : serverEntryFilePath + PASSTHROUGH_QUERY);
|
|
1176
|
+
if (path.normalize(idWithoutQuery) === path.normalize(clientEntryFilePath)) return generateClientEntryCode(userClientEntryPath ? userClientEntryPath + PASSTHROUGH_QUERY : clientEntryFilePath + PASSTHROUGH_QUERY);
|
|
1177
|
+
return null;
|
|
1178
|
+
},
|
|
1179
|
+
configureServer(server) {
|
|
1180
|
+
if (isTestMode || !appDirectory) return;
|
|
1181
|
+
const appDir = appDirectory;
|
|
1182
|
+
const watcher = server.watcher;
|
|
1183
|
+
const checkEntryChange = (filePath) => {
|
|
1184
|
+
const relative$1 = path.relative(appDir, filePath);
|
|
1185
|
+
const basename$1 = path.basename(relative$1, path.extname(relative$1));
|
|
1186
|
+
if (path.dirname(relative$1) !== "." || basename$1 !== "entry.server" && basename$1 !== "entry.client") return;
|
|
1187
|
+
const ext = path.extname(relative$1);
|
|
1188
|
+
if (!ENTRY_EXTENSIONS.includes(ext)) return;
|
|
1189
|
+
const nowHasServer = findUserEntry(appDir, "entry.server") !== void 0;
|
|
1190
|
+
const nowHasClient = findUserEntry(appDir, "entry.client") !== void 0;
|
|
1191
|
+
if (nowHasServer !== (userServerEntryPath !== void 0) || nowHasClient !== (userClientEntryPath !== void 0)) server.restart();
|
|
1192
|
+
};
|
|
1193
|
+
watcher.on("add", checkEntryChange);
|
|
1194
|
+
watcher.on("unlink", checkEntryChange);
|
|
1195
|
+
}
|
|
1196
|
+
};
|
|
1197
|
+
}
|
|
1198
|
+
|
|
1199
|
+
//#endregion
|
|
1200
|
+
//#region src/plugins/workspace.ts
|
|
1201
|
+
/**
|
|
1202
|
+
* Vite plugin that automatically configures workspace-specific settings when
|
|
1203
|
+
* SCAPI_PROXY_HOST is set. This includes:
|
|
1204
|
+
* - Disabling DIS (Dynamic Imaging Service) via PUBLIC__app__images__enableDis
|
|
1205
|
+
* - Adding dev server proxy rules for image paths (/dw/image, /on/demandware.static)
|
|
1206
|
+
* - Allowing all hosts for the dev server (workspace proxies use dynamic hostnames)
|
|
1207
|
+
*
|
|
1208
|
+
* Environment variables:
|
|
1209
|
+
* - `SCAPI_PROXY_HOST` — (Required) Base URL of the SCAPI proxy in workspace environments.
|
|
1210
|
+
* Enables workspace mode when set. Used as the proxy target for SCAPI requests and,
|
|
1211
|
+
* if JWEB_TARGET is not set, for static asset/image paths.
|
|
1212
|
+
* Example: `http://scw:25010`
|
|
1213
|
+
* - `JWEB_TARGET` — (Optional) Separate proxy target for JWeb static asset paths
|
|
1214
|
+
* (`/dw/image`, `/on/demandware.static`). Falls back to SCAPI_PROXY_HOST if not set.
|
|
1215
|
+
* Example: `http://jweb:8080`
|
|
1216
|
+
* - `PUBLIC__app__images__enableDis` — (Auto-set) Set to `'false'` when SCAPI_PROXY_HOST
|
|
1217
|
+
* is present, unless already explicitly configured. Controls whether the template
|
|
1218
|
+
* uses DIS for image format conversion and responsive srcsets.
|
|
1219
|
+
*
|
|
1220
|
+
* In workspace dev mode, this plugin also configures `optimizeDeps.entries` to scan all
|
|
1221
|
+
* source files upfront. Without this, Vite discovers deps lazily per-route and invalidates
|
|
1222
|
+
* the SSR module cache mid-session, leaving React in a partially-initialized state:
|
|
1223
|
+
* TypeError: Cannot read properties of null (reading 'useContext'/'useMemo')
|
|
1224
|
+
*/
|
|
1225
|
+
const workspacePlugin = () => {
|
|
1226
|
+
return {
|
|
1227
|
+
name: "storefront-next-workspace",
|
|
1228
|
+
config(_, { mode }) {
|
|
1229
|
+
const scapiProxyHost = process.env.SCAPI_PROXY_HOST;
|
|
1230
|
+
if (!scapiProxyHost) return;
|
|
1231
|
+
process.env.PUBLIC__app__images__enableDis ??= "false";
|
|
1232
|
+
if (mode !== "development") return;
|
|
1233
|
+
const jwebTarget = process.env.JWEB_TARGET;
|
|
1234
|
+
return {
|
|
1235
|
+
server: {
|
|
1236
|
+
allowedHosts: true,
|
|
1237
|
+
proxy: Object.fromEntries(["/dw/image", "/on/demandware.static"].map((path$2) => [path$2, {
|
|
1238
|
+
target: jwebTarget || scapiProxyHost,
|
|
1239
|
+
changeOrigin: true,
|
|
1240
|
+
secure: false
|
|
1241
|
+
}]))
|
|
1242
|
+
},
|
|
1243
|
+
optimizeDeps: { entries: [
|
|
1244
|
+
"./src/**/*.{ts,tsx}",
|
|
1245
|
+
"!./src/**/*.{test,spec}.{ts,tsx}",
|
|
1246
|
+
"!./src/**/*.stories.{ts,tsx}",
|
|
1247
|
+
"!./src/**/*-snapshot.tsx",
|
|
1248
|
+
"!./src/**/*.d.ts"
|
|
1249
|
+
] }
|
|
1250
|
+
};
|
|
1251
|
+
}
|
|
1252
|
+
};
|
|
1253
|
+
};
|
|
1254
|
+
|
|
1255
|
+
//#endregion
|
|
1256
|
+
//#region src/storefront-next-targets.ts
|
|
1024
1257
|
/**
|
|
1025
1258
|
* Storefront Next Vite plugin that powers the React Router RSC app.
|
|
1026
1259
|
* Supports building and optimizing for the managed runtime environment.
|
|
@@ -1031,16 +1264,16 @@ const buildMiddlewareRegistryPlugin = () => {
|
|
|
1031
1264
|
* @example
|
|
1032
1265
|
* // With default options
|
|
1033
1266
|
* export default defineConfig({
|
|
1034
|
-
* plugins: [
|
|
1267
|
+
* plugins: [storefrontNextTargets()]
|
|
1035
1268
|
* })
|
|
1036
1269
|
*
|
|
1037
1270
|
* @example
|
|
1038
1271
|
* // Disable readable chunk names
|
|
1039
1272
|
* export default defineConfig({
|
|
1040
|
-
* plugins: [
|
|
1273
|
+
* plugins: [storefrontNextTargets({ readableChunkNames: false })]
|
|
1041
1274
|
* })
|
|
1042
1275
|
*/
|
|
1043
|
-
function
|
|
1276
|
+
function storefrontNextTargets(config = {}) {
|
|
1044
1277
|
const { readableChunkNames = false, staticRegistry = {
|
|
1045
1278
|
componentPath: "",
|
|
1046
1279
|
registryPath: "",
|
|
@@ -1052,10 +1285,12 @@ function storefrontNextPlugins(config = {}) {
|
|
|
1052
1285
|
verbose: false
|
|
1053
1286
|
} } = config;
|
|
1054
1287
|
const plugins = [
|
|
1288
|
+
...process.env.SCAPI_PROXY_HOST ? [workspacePlugin()] : [],
|
|
1055
1289
|
managedRuntimeBundlePlugin(),
|
|
1056
1290
|
fixReactRouterManifestUrlsPlugin(),
|
|
1057
1291
|
patchReactRouterPlugin(),
|
|
1058
|
-
|
|
1292
|
+
platformEntryPlugin(),
|
|
1293
|
+
transformTargetPlaceholderPlugin(),
|
|
1059
1294
|
watchConfigFilesPlugin(),
|
|
1060
1295
|
buildMiddlewareRegistryPlugin()
|
|
1061
1296
|
];
|
|
@@ -1065,447 +1300,6 @@ function storefrontNextPlugins(config = {}) {
|
|
|
1065
1300
|
return plugins;
|
|
1066
1301
|
}
|
|
1067
1302
|
|
|
1068
|
-
//#endregion
|
|
1069
|
-
//#region package.json
|
|
1070
|
-
var version = "0.1.1";
|
|
1071
|
-
|
|
1072
|
-
//#endregion
|
|
1073
|
-
//#region src/utils/logger.ts
|
|
1074
|
-
/**
|
|
1075
|
-
* Logger utilities
|
|
1076
|
-
*/
|
|
1077
|
-
const colors = {
|
|
1078
|
-
warn: "yellow",
|
|
1079
|
-
error: "red",
|
|
1080
|
-
success: "cyan",
|
|
1081
|
-
info: "green",
|
|
1082
|
-
debug: "gray"
|
|
1083
|
-
};
|
|
1084
|
-
const fancyLog = (level, msg) => {
|
|
1085
|
-
const colorFn = chalk[colors[level]];
|
|
1086
|
-
console.log(`${colorFn(level)}: ${msg}`);
|
|
1087
|
-
};
|
|
1088
|
-
const info = (msg) => fancyLog("info", msg);
|
|
1089
|
-
const success = (msg) => fancyLog("success", msg);
|
|
1090
|
-
const warn = (msg) => fancyLog("warn", msg);
|
|
1091
|
-
const error = (msg) => fancyLog("error", msg);
|
|
1092
|
-
const debug = (msg, data) => {
|
|
1093
|
-
if (process.env.DEBUG || process.env.NODE_ENV !== "production") {
|
|
1094
|
-
fancyLog("debug", msg);
|
|
1095
|
-
if (data) console.log(data);
|
|
1096
|
-
}
|
|
1097
|
-
};
|
|
1098
|
-
|
|
1099
|
-
//#endregion
|
|
1100
|
-
//#region src/utils.ts
|
|
1101
|
-
const DEFAULT_CLOUD_ORIGIN = "https://cloud.mobify.com";
|
|
1102
|
-
const getDefaultBuildDir = (targetDir) => path$1.join(targetDir, "build");
|
|
1103
|
-
const NODE_ENV = process.env.NODE_ENV || "development";
|
|
1104
|
-
/**
|
|
1105
|
-
* Get credentials file path based on cloud origin
|
|
1106
|
-
*/
|
|
1107
|
-
const getCredentialsFile = (cloudOrigin, credentialsFile) => {
|
|
1108
|
-
if (credentialsFile) return credentialsFile;
|
|
1109
|
-
const host = new URL(cloudOrigin).host;
|
|
1110
|
-
const suffix = host === "cloud.mobify.com" ? "" : `--${host}`;
|
|
1111
|
-
return path$1.join(os.homedir(), `.mobify${suffix}`);
|
|
1112
|
-
};
|
|
1113
|
-
/**
|
|
1114
|
-
* Read credentials from file
|
|
1115
|
-
*/
|
|
1116
|
-
const readCredentials = async (filepath) => {
|
|
1117
|
-
try {
|
|
1118
|
-
const data = await fs.readJSON(filepath);
|
|
1119
|
-
return {
|
|
1120
|
-
username: data.username,
|
|
1121
|
-
api_key: data.api_key
|
|
1122
|
-
};
|
|
1123
|
-
} catch {
|
|
1124
|
-
throw new Error(`Credentials file "${filepath}" not found.\nVisit https://runtime.commercecloud.com/account/settings for steps on authorizing your computer to push bundles.`);
|
|
1125
|
-
}
|
|
1126
|
-
};
|
|
1127
|
-
/**
|
|
1128
|
-
* Get project package.json
|
|
1129
|
-
*/
|
|
1130
|
-
const getProjectPkg = (projectDir) => {
|
|
1131
|
-
const packagePath = path$1.join(projectDir, "package.json");
|
|
1132
|
-
try {
|
|
1133
|
-
return fs.readJSONSync(packagePath);
|
|
1134
|
-
} catch {
|
|
1135
|
-
throw new Error(`Could not read project package at "${packagePath}"`);
|
|
1136
|
-
}
|
|
1137
|
-
};
|
|
1138
|
-
/**
|
|
1139
|
-
* Load .env file from project directory
|
|
1140
|
-
*/
|
|
1141
|
-
const loadEnvFile = (projectDir) => {
|
|
1142
|
-
const envPath = path$1.join(projectDir, ".env");
|
|
1143
|
-
if (fs.existsSync(envPath)) dotenv.config({ path: envPath });
|
|
1144
|
-
else warn("No .env file found");
|
|
1145
|
-
};
|
|
1146
|
-
/**
|
|
1147
|
-
* Get MRT configuration with priority logic: .env -> package.json -> defaults
|
|
1148
|
-
*/
|
|
1149
|
-
const getMrtConfig = (projectDir) => {
|
|
1150
|
-
loadEnvFile(projectDir);
|
|
1151
|
-
const pkg = getProjectPkg(projectDir);
|
|
1152
|
-
const defaultMrtProject = process.env.MRT_PROJECT ?? pkg.name;
|
|
1153
|
-
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.");
|
|
1154
|
-
const defaultMrtTarget = process.env.MRT_TARGET ?? void 0;
|
|
1155
|
-
debug("MRT configuration resolved", {
|
|
1156
|
-
projectDir,
|
|
1157
|
-
envMrtProject: process.env.MRT_PROJECT,
|
|
1158
|
-
envMrtTarget: process.env.MRT_TARGET,
|
|
1159
|
-
packageName: pkg.name,
|
|
1160
|
-
resolvedProject: defaultMrtProject,
|
|
1161
|
-
resolvedTarget: defaultMrtTarget
|
|
1162
|
-
});
|
|
1163
|
-
return {
|
|
1164
|
-
defaultMrtProject,
|
|
1165
|
-
defaultMrtTarget
|
|
1166
|
-
};
|
|
1167
|
-
};
|
|
1168
|
-
/**
|
|
1169
|
-
* Get project dependency tree (simplified version)
|
|
1170
|
-
*/
|
|
1171
|
-
const getProjectDependencyTree = (projectDir) => {
|
|
1172
|
-
try {
|
|
1173
|
-
const tmpFile = path$1.join(os.tmpdir(), `npm-ls-${Date.now()}.json`);
|
|
1174
|
-
execSync(`npm ls --all --json > ${tmpFile}`, {
|
|
1175
|
-
stdio: "ignore",
|
|
1176
|
-
cwd: projectDir
|
|
1177
|
-
});
|
|
1178
|
-
const data = fs.readJSONSync(tmpFile);
|
|
1179
|
-
fs.unlinkSync(tmpFile);
|
|
1180
|
-
return data;
|
|
1181
|
-
} catch {
|
|
1182
|
-
return null;
|
|
1183
|
-
}
|
|
1184
|
-
};
|
|
1185
|
-
/**
|
|
1186
|
-
* Get PWA Kit dependencies from dependency tree
|
|
1187
|
-
*/
|
|
1188
|
-
const getPwaKitDependencies = (dependencyTree) => {
|
|
1189
|
-
if (!dependencyTree) return {};
|
|
1190
|
-
const pwaKitDependencies = ["@salesforce/storefront-next-dev"];
|
|
1191
|
-
const result = {};
|
|
1192
|
-
const searchDeps = (tree) => {
|
|
1193
|
-
if (tree.dependencies) for (const [name, dep] of Object.entries(tree.dependencies)) {
|
|
1194
|
-
if (pwaKitDependencies.includes(name)) result[name] = dep.version || "unknown";
|
|
1195
|
-
if (dep.dependencies) searchDeps({ dependencies: dep.dependencies });
|
|
1196
|
-
}
|
|
1197
|
-
};
|
|
1198
|
-
searchDeps(dependencyTree);
|
|
1199
|
-
return result;
|
|
1200
|
-
};
|
|
1201
|
-
/**
|
|
1202
|
-
* Get default commit message from git
|
|
1203
|
-
*/
|
|
1204
|
-
const getDefaultMessage = (projectDir) => {
|
|
1205
|
-
try {
|
|
1206
|
-
return `${execSync("git rev-parse --abbrev-ref HEAD", {
|
|
1207
|
-
encoding: "utf8",
|
|
1208
|
-
cwd: projectDir
|
|
1209
|
-
}).trim()}: ${execSync("git rev-parse --short HEAD", {
|
|
1210
|
-
encoding: "utf8",
|
|
1211
|
-
cwd: projectDir
|
|
1212
|
-
}).trim()}`;
|
|
1213
|
-
} catch {
|
|
1214
|
-
debug("Using default bundle message as no message was provided and not in a Git repo.");
|
|
1215
|
-
return "PWA Kit Bundle";
|
|
1216
|
-
}
|
|
1217
|
-
};
|
|
1218
|
-
|
|
1219
|
-
//#endregion
|
|
1220
|
-
//#region src/bundle.ts
|
|
1221
|
-
/**
|
|
1222
|
-
* Create a bundle from the build directory
|
|
1223
|
-
*/
|
|
1224
|
-
const createBundle = async (options) => {
|
|
1225
|
-
const { message, ssr_parameters, ssr_only, ssr_shared, buildDirectory, projectDirectory, projectSlug } = options;
|
|
1226
|
-
const tmpDir = fs.mkdtempSync(path$1.join(os.tmpdir(), "storefront-next-dev-push-"));
|
|
1227
|
-
const destination = path$1.join(tmpDir, "build.tar");
|
|
1228
|
-
const filesInArchive = [];
|
|
1229
|
-
if (!ssr_only || ssr_only.length === 0 || !ssr_shared || ssr_shared.length === 0) throw new Error("no ssrOnly or ssrShared files are defined");
|
|
1230
|
-
return new Promise((resolve$2, reject) => {
|
|
1231
|
-
const output = fs.createWriteStream(destination);
|
|
1232
|
-
const archive = archiver("tar");
|
|
1233
|
-
archive.pipe(output);
|
|
1234
|
-
const newRoot = path$1.join(projectSlug, "bld", "");
|
|
1235
|
-
const storybookExclusionMatchers = [
|
|
1236
|
-
"**/*.stories.tsx",
|
|
1237
|
-
"**/*.stories.ts",
|
|
1238
|
-
"**/*-snapshot.tsx",
|
|
1239
|
-
".storybook/**/*",
|
|
1240
|
-
"storybook-static/**/*",
|
|
1241
|
-
"**/__mocks__/**/*",
|
|
1242
|
-
"**/__snapshots__/**/*"
|
|
1243
|
-
].map((pattern) => new Minimatch(pattern, { nocomment: true }));
|
|
1244
|
-
archive.directory(buildDirectory, "", (entry) => {
|
|
1245
|
-
if (entry.name && storybookExclusionMatchers.some((matcher) => matcher.match(entry.name))) return false;
|
|
1246
|
-
if (entry.stats?.isFile() && entry.name) filesInArchive.push(entry.name);
|
|
1247
|
-
entry.prefix = newRoot;
|
|
1248
|
-
return entry;
|
|
1249
|
-
});
|
|
1250
|
-
archive.on("error", reject);
|
|
1251
|
-
output.on("finish", () => {
|
|
1252
|
-
try {
|
|
1253
|
-
const { dependencies = {}, devDependencies = {} } = getProjectPkg(projectDirectory);
|
|
1254
|
-
const dependencyTree = getProjectDependencyTree(projectDirectory);
|
|
1255
|
-
const pwaKitDeps = dependencyTree ? getPwaKitDependencies(dependencyTree) : {};
|
|
1256
|
-
const bundle_metadata = { dependencies: {
|
|
1257
|
-
...dependencies,
|
|
1258
|
-
...devDependencies,
|
|
1259
|
-
...pwaKitDeps
|
|
1260
|
-
} };
|
|
1261
|
-
const data = fs.readFileSync(destination);
|
|
1262
|
-
const encoding = "base64";
|
|
1263
|
-
fs.rmSync(tmpDir, { recursive: true });
|
|
1264
|
-
const createGlobMatcher = (patterns) => {
|
|
1265
|
-
const allPatterns = patterns.map((pattern) => new Minimatch(pattern, { nocomment: true })).filter((pattern) => !pattern.empty);
|
|
1266
|
-
const positivePatterns = allPatterns.filter((pattern) => !pattern.negate);
|
|
1267
|
-
const negativePatterns = allPatterns.filter((pattern) => pattern.negate);
|
|
1268
|
-
return (filePath) => {
|
|
1269
|
-
if (filePath) {
|
|
1270
|
-
const positive = positivePatterns.some((pattern) => pattern.match(filePath));
|
|
1271
|
-
const negative = negativePatterns.some((pattern) => !pattern.match(filePath));
|
|
1272
|
-
return positive && !negative;
|
|
1273
|
-
}
|
|
1274
|
-
return false;
|
|
1275
|
-
};
|
|
1276
|
-
};
|
|
1277
|
-
resolve$2({
|
|
1278
|
-
message,
|
|
1279
|
-
encoding,
|
|
1280
|
-
data: data.toString(encoding),
|
|
1281
|
-
ssr_parameters,
|
|
1282
|
-
ssr_only: filesInArchive.filter(createGlobMatcher(ssr_only)),
|
|
1283
|
-
ssr_shared: filesInArchive.filter(createGlobMatcher(ssr_shared)),
|
|
1284
|
-
bundle_metadata
|
|
1285
|
-
});
|
|
1286
|
-
} catch (err) {
|
|
1287
|
-
reject(err);
|
|
1288
|
-
}
|
|
1289
|
-
});
|
|
1290
|
-
archive.finalize().catch(reject);
|
|
1291
|
-
});
|
|
1292
|
-
};
|
|
1293
|
-
|
|
1294
|
-
//#endregion
|
|
1295
|
-
//#region src/cloud-api.ts
|
|
1296
|
-
var CloudAPIClient = class {
|
|
1297
|
-
credentials;
|
|
1298
|
-
origin;
|
|
1299
|
-
constructor({ credentials, origin }) {
|
|
1300
|
-
this.credentials = credentials;
|
|
1301
|
-
this.origin = origin;
|
|
1302
|
-
}
|
|
1303
|
-
getAuthHeader() {
|
|
1304
|
-
const { username, api_key } = this.credentials;
|
|
1305
|
-
return { Authorization: `Basic ${Buffer.from(`${username}:${api_key}`, "binary").toString("base64")}` };
|
|
1306
|
-
}
|
|
1307
|
-
getHeaders() {
|
|
1308
|
-
return {
|
|
1309
|
-
"User-Agent": `storefront-next-dev@${version}`,
|
|
1310
|
-
...this.getAuthHeader()
|
|
1311
|
-
};
|
|
1312
|
-
}
|
|
1313
|
-
/**
|
|
1314
|
-
* Push bundle to Managed Runtime
|
|
1315
|
-
*/
|
|
1316
|
-
async push(bundle, projectSlug, target) {
|
|
1317
|
-
const base = `api/projects/${projectSlug}/builds/`;
|
|
1318
|
-
const pathname = target ? `${base}${target}/` : base;
|
|
1319
|
-
const url = new URL$1(this.origin);
|
|
1320
|
-
url.pathname = pathname;
|
|
1321
|
-
const body = Buffer.from(JSON.stringify(bundle));
|
|
1322
|
-
const headers = {
|
|
1323
|
-
...this.getHeaders(),
|
|
1324
|
-
"Content-Length": body.length.toString()
|
|
1325
|
-
};
|
|
1326
|
-
const res = await fetch(url.toString(), {
|
|
1327
|
-
body,
|
|
1328
|
-
method: "POST",
|
|
1329
|
-
headers
|
|
1330
|
-
});
|
|
1331
|
-
if (res.status >= 400) {
|
|
1332
|
-
const bodyText = await res.text();
|
|
1333
|
-
let errorData;
|
|
1334
|
-
try {
|
|
1335
|
-
errorData = JSON.parse(bodyText);
|
|
1336
|
-
} catch {
|
|
1337
|
-
errorData = { message: bodyText };
|
|
1338
|
-
}
|
|
1339
|
-
throw new Error(`HTTP ${res.status}: ${errorData.message || bodyText}\nFor more information visit https://developer.salesforce.com/docs/commerce/pwa-kit-managed-runtime/guide/pushing-and-deploying-bundles.html`);
|
|
1340
|
-
}
|
|
1341
|
-
return await res.json();
|
|
1342
|
-
}
|
|
1343
|
-
/**
|
|
1344
|
-
* Wait for deployment to complete
|
|
1345
|
-
*/
|
|
1346
|
-
async waitForDeploy(project, environment) {
|
|
1347
|
-
return new Promise((resolve$2, reject) => {
|
|
1348
|
-
const delay = 3e4;
|
|
1349
|
-
const check = async () => {
|
|
1350
|
-
const url = new URL$1(`/api/projects/${project}/target/${environment}`, this.origin);
|
|
1351
|
-
const res = await fetch(url, { headers: this.getHeaders() });
|
|
1352
|
-
if (!res.ok) {
|
|
1353
|
-
const text = await res.text();
|
|
1354
|
-
let json;
|
|
1355
|
-
try {
|
|
1356
|
-
if (text) json = JSON.parse(text);
|
|
1357
|
-
} catch {}
|
|
1358
|
-
const message = json?.detail ?? text;
|
|
1359
|
-
const detail = message ? `: ${message}` : "";
|
|
1360
|
-
throw new Error(`${res.status} ${res.statusText}${detail}`);
|
|
1361
|
-
}
|
|
1362
|
-
const data = await res.json();
|
|
1363
|
-
if (typeof data.state !== "string") return reject(/* @__PURE__ */ new Error("An unknown state occurred when polling the deployment."));
|
|
1364
|
-
switch (data.state) {
|
|
1365
|
-
case "CREATE_IN_PROGRESS":
|
|
1366
|
-
case "PUBLISH_IN_PROGRESS":
|
|
1367
|
-
setTimeout(() => {
|
|
1368
|
-
check().catch(reject);
|
|
1369
|
-
}, delay);
|
|
1370
|
-
return;
|
|
1371
|
-
case "CREATE_FAILED":
|
|
1372
|
-
case "PUBLISH_FAILED": return reject(/* @__PURE__ */ new Error("Deployment failed."));
|
|
1373
|
-
case "ACTIVE": return resolve$2();
|
|
1374
|
-
default: return reject(/* @__PURE__ */ new Error(`Unknown deployment state "${data.state}".`));
|
|
1375
|
-
}
|
|
1376
|
-
};
|
|
1377
|
-
setTimeout(() => {
|
|
1378
|
-
check().catch(reject);
|
|
1379
|
-
}, delay);
|
|
1380
|
-
});
|
|
1381
|
-
}
|
|
1382
|
-
};
|
|
1383
|
-
|
|
1384
|
-
//#endregion
|
|
1385
|
-
//#region src/config.ts
|
|
1386
|
-
const SFNEXT_BASE_CARTRIDGE_NAME = "app_storefrontnext_base";
|
|
1387
|
-
const SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR = `${SFNEXT_BASE_CARTRIDGE_NAME}/cartridge/experience`;
|
|
1388
|
-
/**
|
|
1389
|
-
* Build MRT SSR configuration for bundle deployment
|
|
1390
|
-
*
|
|
1391
|
-
* Defines which files should be:
|
|
1392
|
-
* - Server-only (ssrOnly): Deployed only to Lambda functions
|
|
1393
|
-
* - Shared (ssrShared): Deployed to both Lambda and CDN
|
|
1394
|
-
*
|
|
1395
|
-
* @param buildDirectory - Path to the build output directory
|
|
1396
|
-
* @param projectDirectory - Path to the project root (reserved for future use)
|
|
1397
|
-
* @returns MRT SSR configuration with glob patterns
|
|
1398
|
-
*/
|
|
1399
|
-
const buildMrtConfig = (_buildDirectory, _projectDirectory) => {
|
|
1400
|
-
const ssrEntryPoint = getMrtEntryFile("production");
|
|
1401
|
-
return {
|
|
1402
|
-
ssrOnly: [
|
|
1403
|
-
"server/**/*",
|
|
1404
|
-
"loader.js",
|
|
1405
|
-
`${ssrEntryPoint}.{js,mjs,cjs}`,
|
|
1406
|
-
`${ssrEntryPoint}.{js,mjs,cjs}.map`,
|
|
1407
|
-
"!static/**/*",
|
|
1408
|
-
"sfnext-server-*.mjs",
|
|
1409
|
-
"!**/*.stories.tsx",
|
|
1410
|
-
"!**/*.stories.ts",
|
|
1411
|
-
"!**/*-snapshot.tsx",
|
|
1412
|
-
"!.storybook/**/*",
|
|
1413
|
-
"!storybook-static/**/*",
|
|
1414
|
-
"!**/__mocks__/**/*",
|
|
1415
|
-
"!**/__snapshots__/**/*"
|
|
1416
|
-
],
|
|
1417
|
-
ssrShared: [
|
|
1418
|
-
"client/**/*",
|
|
1419
|
-
"static/**/*",
|
|
1420
|
-
"**/*.css",
|
|
1421
|
-
"**/*.png",
|
|
1422
|
-
"**/*.jpg",
|
|
1423
|
-
"**/*.jpeg",
|
|
1424
|
-
"**/*.gif",
|
|
1425
|
-
"**/*.svg",
|
|
1426
|
-
"**/*.ico",
|
|
1427
|
-
"**/*.woff",
|
|
1428
|
-
"**/*.woff2",
|
|
1429
|
-
"**/*.ttf",
|
|
1430
|
-
"**/*.eot",
|
|
1431
|
-
"!**/*.stories.tsx",
|
|
1432
|
-
"!**/*.stories.ts",
|
|
1433
|
-
"!**/*-snapshot.tsx",
|
|
1434
|
-
"!.storybook/**/*",
|
|
1435
|
-
"!storybook-static/**/*",
|
|
1436
|
-
"!**/__mocks__/**/*",
|
|
1437
|
-
"!**/__snapshots__/**/*"
|
|
1438
|
-
],
|
|
1439
|
-
ssrParameters: { ssrFunctionNodeVersion: "24.x" }
|
|
1440
|
-
};
|
|
1441
|
-
};
|
|
1442
|
-
|
|
1443
|
-
//#endregion
|
|
1444
|
-
//#region src/commands/push.ts
|
|
1445
|
-
/**
|
|
1446
|
-
* Main function to push bundle to Managed Runtime
|
|
1447
|
-
*/
|
|
1448
|
-
async function push(options) {
|
|
1449
|
-
const mrtConfig = getMrtConfig(options.projectDirectory);
|
|
1450
|
-
const resolvedTarget = options.target ?? mrtConfig.defaultMrtTarget;
|
|
1451
|
-
if (options.wait && !resolvedTarget) throw new Error("You must provide a target to deploy to when using --wait (via --target flag or .env MRT_TARGET)");
|
|
1452
|
-
if (options.user && !options.key || !options.user && options.key) throw new Error("You must provide both --user and --key together, or neither");
|
|
1453
|
-
if (!fs.existsSync(options.projectDirectory)) throw new Error(`Project directory "${options.projectDirectory}" does not exist!`);
|
|
1454
|
-
const projectSlug = options.projectSlug ?? mrtConfig.defaultMrtProject;
|
|
1455
|
-
if (!projectSlug || projectSlug.trim() === "") throw new Error("Project slug could not be determined from CLI, .env, or package.json");
|
|
1456
|
-
const target = resolvedTarget;
|
|
1457
|
-
const buildDirectory = options.buildDirectory ?? getDefaultBuildDir(options.projectDirectory);
|
|
1458
|
-
if (!fs.existsSync(buildDirectory)) throw new Error(`Build directory "${buildDirectory}" does not exist!`);
|
|
1459
|
-
try {
|
|
1460
|
-
if (target) process.env.DEPLOY_TARGET = target;
|
|
1461
|
-
let credentials;
|
|
1462
|
-
if (options.user && options.key) credentials = {
|
|
1463
|
-
username: options.user,
|
|
1464
|
-
api_key: options.key
|
|
1465
|
-
};
|
|
1466
|
-
else credentials = await readCredentials(getCredentialsFile(options.cloudOrigin ?? DEFAULT_CLOUD_ORIGIN, options.credentialsFile));
|
|
1467
|
-
const config = buildMrtConfig(buildDirectory, options.projectDirectory);
|
|
1468
|
-
const message = options.message ?? getDefaultMessage(options.projectDirectory);
|
|
1469
|
-
info(`Creating bundle for project: ${projectSlug}`);
|
|
1470
|
-
if (options.projectSlug) debug("Using project slug from CLI argument");
|
|
1471
|
-
else if (process.env.MRT_PROJECT) debug("Using project slug from .env MRT_PROJECT");
|
|
1472
|
-
else debug("Using project slug from package.json name");
|
|
1473
|
-
if (target) {
|
|
1474
|
-
info(`Target environment: ${target}`);
|
|
1475
|
-
if (options.target) debug("Using target from CLI argument");
|
|
1476
|
-
else debug("Using target from .env");
|
|
1477
|
-
}
|
|
1478
|
-
debug("SSR shared files", config.ssrShared);
|
|
1479
|
-
debug("SSR only files", config.ssrOnly);
|
|
1480
|
-
const bundle = await createBundle({
|
|
1481
|
-
message,
|
|
1482
|
-
ssr_parameters: config.ssrParameters,
|
|
1483
|
-
ssr_only: config.ssrOnly,
|
|
1484
|
-
ssr_shared: config.ssrShared,
|
|
1485
|
-
buildDirectory,
|
|
1486
|
-
projectDirectory: options.projectDirectory,
|
|
1487
|
-
projectSlug
|
|
1488
|
-
});
|
|
1489
|
-
const client = new CloudAPIClient({
|
|
1490
|
-
credentials,
|
|
1491
|
-
origin: options.cloudOrigin ?? DEFAULT_CLOUD_ORIGIN
|
|
1492
|
-
});
|
|
1493
|
-
info(`Beginning upload to ${options.cloudOrigin ?? DEFAULT_CLOUD_ORIGIN}`);
|
|
1494
|
-
const data = await client.push(bundle, projectSlug, target);
|
|
1495
|
-
debug("API response", data);
|
|
1496
|
-
(data.warnings || []).forEach(warn);
|
|
1497
|
-
if (options.wait && target) {
|
|
1498
|
-
success("Bundle uploaded - waiting for deployment to complete");
|
|
1499
|
-
await client.waitForDeploy(projectSlug, target);
|
|
1500
|
-
success("Deployment complete!");
|
|
1501
|
-
} else success("Bundle uploaded successfully!");
|
|
1502
|
-
if (data.url) info(`Bundle URL: ${data.url}`);
|
|
1503
|
-
} catch (err) {
|
|
1504
|
-
error(err.message || err?.toString() || "Unknown error");
|
|
1505
|
-
throw err;
|
|
1506
|
-
}
|
|
1507
|
-
}
|
|
1508
|
-
|
|
1509
1303
|
//#endregion
|
|
1510
1304
|
//#region src/server/ts-import.ts
|
|
1511
1305
|
/**
|
|
@@ -1534,7 +1328,11 @@ function parseTsconfigPaths(tsconfigPath, projectDirectory) {
|
|
|
1534
1328
|
}
|
|
1535
1329
|
}
|
|
1536
1330
|
} catch {}
|
|
1537
|
-
|
|
1331
|
+
const sortedAlias = {};
|
|
1332
|
+
Object.keys(alias).sort((a, b) => b.length - a.length).forEach((key) => {
|
|
1333
|
+
sortedAlias[key] = alias[key];
|
|
1334
|
+
});
|
|
1335
|
+
return sortedAlias;
|
|
1538
1336
|
}
|
|
1539
1337
|
/**
|
|
1540
1338
|
* Import a TypeScript file using jiti with proper path alias resolution.
|
|
@@ -1571,16 +1369,18 @@ function loadConfigFromEnv() {
|
|
|
1571
1369
|
const clientId = process.env.PUBLIC__app__commerce__api__clientId;
|
|
1572
1370
|
const siteId = process.env.PUBLIC__app__commerce__api__siteId;
|
|
1573
1371
|
const proxy = process.env.PUBLIC__app__commerce__api__proxy || "/mobify/proxy/api";
|
|
1574
|
-
|
|
1372
|
+
const proxyHost = process.env.SCAPI_PROXY_HOST;
|
|
1373
|
+
if (!shortCode && !proxyHost) throw new Error("Missing PUBLIC__app__commerce__api__shortCode environment variable.\nPlease set it in your .env file or environment.");
|
|
1575
1374
|
if (!organizationId) throw new Error("Missing PUBLIC__app__commerce__api__organizationId environment variable.\nPlease set it in your .env file or environment.");
|
|
1576
1375
|
if (!clientId) throw new Error("Missing PUBLIC__app__commerce__api__clientId environment variable.\nPlease set it in your .env file or environment.");
|
|
1577
1376
|
if (!siteId) throw new Error("Missing PUBLIC__app__commerce__api__siteId environment variable.\nPlease set it in your .env file or environment.");
|
|
1578
1377
|
return { commerce: { api: {
|
|
1579
|
-
shortCode,
|
|
1378
|
+
shortCode: shortCode || "",
|
|
1580
1379
|
organizationId,
|
|
1581
1380
|
clientId,
|
|
1582
1381
|
siteId,
|
|
1583
|
-
proxy
|
|
1382
|
+
proxy,
|
|
1383
|
+
proxyHost
|
|
1584
1384
|
} } };
|
|
1585
1385
|
}
|
|
1586
1386
|
/**
|
|
@@ -1600,49 +1400,21 @@ async function loadProjectConfig(projectDirectory) {
|
|
|
1600
1400
|
})).default;
|
|
1601
1401
|
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.");
|
|
1602
1402
|
const api = config.app.commerce.api;
|
|
1603
|
-
|
|
1403
|
+
const proxyHost = process.env.SCAPI_PROXY_HOST;
|
|
1404
|
+
if (!api.shortCode && !proxyHost) throw new Error("Missing shortCode in config.server.ts commerce.api configuration");
|
|
1604
1405
|
if (!api.organizationId) throw new Error("Missing organizationId in config.server.ts commerce.api configuration");
|
|
1605
1406
|
if (!api.clientId) throw new Error("Missing clientId in config.server.ts commerce.api configuration");
|
|
1606
1407
|
if (!api.siteId) throw new Error("Missing siteId in config.server.ts commerce.api configuration");
|
|
1607
1408
|
return { commerce: { api: {
|
|
1608
|
-
shortCode: api.shortCode,
|
|
1409
|
+
shortCode: api.shortCode || "",
|
|
1609
1410
|
organizationId: api.organizationId,
|
|
1610
1411
|
clientId: api.clientId,
|
|
1611
1412
|
siteId: api.siteId,
|
|
1612
|
-
proxy: api.proxy || "/mobify/proxy/api"
|
|
1413
|
+
proxy: api.proxy || "/mobify/proxy/api",
|
|
1414
|
+
proxyHost
|
|
1613
1415
|
} } };
|
|
1614
1416
|
}
|
|
1615
1417
|
|
|
1616
|
-
//#endregion
|
|
1617
|
-
//#region src/utils/paths.ts
|
|
1618
|
-
/**
|
|
1619
|
-
* Copyright 2026 Salesforce, Inc.
|
|
1620
|
-
*
|
|
1621
|
-
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
1622
|
-
* you may not use this file except in compliance with the License.
|
|
1623
|
-
* You may obtain a copy of the License at
|
|
1624
|
-
*
|
|
1625
|
-
* http://www.apache.org/licenses/LICENSE-2.0
|
|
1626
|
-
*
|
|
1627
|
-
* Unless required by applicable law or agreed to in writing, software
|
|
1628
|
-
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
1629
|
-
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
1630
|
-
* See the License for the specific language governing permissions and
|
|
1631
|
-
* limitations under the License.
|
|
1632
|
-
*/
|
|
1633
|
-
/**
|
|
1634
|
-
* Get the Commerce Cloud API URL from a short code
|
|
1635
|
-
*/
|
|
1636
|
-
function getCommerceCloudApiUrl(shortCode) {
|
|
1637
|
-
return `https://${shortCode}.api.commercecloud.salesforce.com`;
|
|
1638
|
-
}
|
|
1639
|
-
/**
|
|
1640
|
-
* Get the bundle path for static assets
|
|
1641
|
-
*/
|
|
1642
|
-
function getBundlePath(bundleId) {
|
|
1643
|
-
return `/mobify/bundle/${bundleId}/client/`;
|
|
1644
|
-
}
|
|
1645
|
-
|
|
1646
1418
|
//#endregion
|
|
1647
1419
|
//#region src/server/middleware/proxy.ts
|
|
1648
1420
|
/**
|
|
@@ -1651,11 +1423,31 @@ function getBundlePath(bundleId) {
|
|
|
1651
1423
|
*/
|
|
1652
1424
|
function createCommerceProxyMiddleware(config) {
|
|
1653
1425
|
return createProxyMiddleware({
|
|
1654
|
-
target: getCommerceCloudApiUrl(config.commerce.api.shortCode),
|
|
1655
|
-
changeOrigin: true
|
|
1426
|
+
target: getCommerceCloudApiUrl(config.commerce.api.shortCode, config.commerce.api.proxyHost),
|
|
1427
|
+
changeOrigin: true,
|
|
1428
|
+
secure: !config.commerce.api.proxyHost
|
|
1656
1429
|
});
|
|
1657
1430
|
}
|
|
1658
1431
|
|
|
1432
|
+
//#endregion
|
|
1433
|
+
//#region src/utils/logger.ts
|
|
1434
|
+
/**
|
|
1435
|
+
* Logger utilities
|
|
1436
|
+
*/
|
|
1437
|
+
const colors = {
|
|
1438
|
+
warn: "yellow",
|
|
1439
|
+
error: "red",
|
|
1440
|
+
success: "cyan",
|
|
1441
|
+
info: "green",
|
|
1442
|
+
debug: "gray"
|
|
1443
|
+
};
|
|
1444
|
+
const fancyLog = (level, msg) => {
|
|
1445
|
+
const colorFn = chalk[colors[level]];
|
|
1446
|
+
console.log(`${colorFn(level)}: ${msg}`);
|
|
1447
|
+
};
|
|
1448
|
+
const info = (msg) => fancyLog("info", msg);
|
|
1449
|
+
const warn = (msg) => fancyLog("warn", msg);
|
|
1450
|
+
|
|
1659
1451
|
//#endregion
|
|
1660
1452
|
//#region src/server/middleware/static.ts
|
|
1661
1453
|
/**
|
|
@@ -1907,9 +1699,9 @@ async function createSSRHandler(mode, bundleId, vite, build, enableAssetUrlPatch
|
|
|
1907
1699
|
build: await ssrEnvironment.runner.import("virtual:react-router/server-build"),
|
|
1908
1700
|
mode: process.env.NODE_ENV
|
|
1909
1701
|
})(req, res, next);
|
|
1910
|
-
} catch (error
|
|
1911
|
-
vite.ssrFixStacktrace(error
|
|
1912
|
-
next(error
|
|
1702
|
+
} catch (error) {
|
|
1703
|
+
vite.ssrFixStacktrace(error);
|
|
1704
|
+
next(error);
|
|
1913
1705
|
}
|
|
1914
1706
|
};
|
|
1915
1707
|
} else if (build) {
|
|
@@ -1945,11 +1737,11 @@ function trimExtensions(directory, selectedExtensions, extensionConfig, verboseO
|
|
|
1945
1737
|
verbose = verboseOverride ?? false;
|
|
1946
1738
|
const configuredExtensions = extensionConfig?.extensions || {};
|
|
1947
1739
|
const extensions = {};
|
|
1948
|
-
Object.keys(configuredExtensions).forEach((
|
|
1949
|
-
extensions[
|
|
1740
|
+
Object.keys(configuredExtensions).forEach((targetKey) => {
|
|
1741
|
+
extensions[targetKey] = Boolean(selectedExtensions?.[targetKey]) || false;
|
|
1950
1742
|
});
|
|
1951
1743
|
if (Object.keys(extensions).length === 0) {
|
|
1952
|
-
if (verbose) console.log("No
|
|
1744
|
+
if (verbose) console.log("No targets found, skipping trim");
|
|
1953
1745
|
return;
|
|
1954
1746
|
}
|
|
1955
1747
|
const processDirectory = (dir) => {
|
|
@@ -2000,8 +1792,8 @@ function processFile(filePath, extensions) {
|
|
|
2000
1792
|
fs$1.unlinkSync(filePath);
|
|
2001
1793
|
if (verbose) console.log(`Deleted file ${filePath}`);
|
|
2002
1794
|
} catch (e) {
|
|
2003
|
-
const error
|
|
2004
|
-
console.error(`Error deleting file ${filePath}: ${error
|
|
1795
|
+
const error = e;
|
|
1796
|
+
console.error(`Error deleting file ${filePath}: ${error.message}`);
|
|
2005
1797
|
throw e;
|
|
2006
1798
|
}
|
|
2007
1799
|
return;
|
|
@@ -2053,8 +1845,8 @@ function processFile(filePath, extensions) {
|
|
|
2053
1845
|
fs$1.writeFileSync(filePath, newSource);
|
|
2054
1846
|
if (verbose) console.log(`Updated file ${filePath}`);
|
|
2055
1847
|
} catch (e) {
|
|
2056
|
-
const error
|
|
2057
|
-
console.error(`Error updating file ${filePath}: ${error
|
|
1848
|
+
const error = e;
|
|
1849
|
+
console.error(`Error updating file ${filePath}: ${error.message}`);
|
|
2058
1850
|
throw e;
|
|
2059
1851
|
}
|
|
2060
1852
|
}
|
|
@@ -2080,9 +1872,9 @@ function deleteExtensionFolders(projectRoot, extensions, extensionConfig) {
|
|
|
2080
1872
|
});
|
|
2081
1873
|
if (verbose) console.log(`Deleted extension folder: ${extensionFolderPath}`);
|
|
2082
1874
|
} catch (err) {
|
|
2083
|
-
const error
|
|
2084
|
-
if (error
|
|
2085
|
-
else console.error(`Error deleting ${extensionFolderPath}: ${error
|
|
1875
|
+
const error = err;
|
|
1876
|
+
if (error.code === "EPERM") console.error(`Permission denied - cannot delete ${extensionFolderPath}. You may need to run with sudo or check permissions.`);
|
|
1877
|
+
else console.error(`Error deleting ${extensionFolderPath}: ${error.message}`);
|
|
2086
1878
|
}
|
|
2087
1879
|
}
|
|
2088
1880
|
});
|
|
@@ -2094,7 +1886,7 @@ let isCliAvailable = null;
|
|
|
2094
1886
|
function checkReactRouterCli(projectDirectory) {
|
|
2095
1887
|
if (isCliAvailable !== null) return isCliAvailable;
|
|
2096
1888
|
try {
|
|
2097
|
-
execSync
|
|
1889
|
+
execSync("react-router --version", {
|
|
2098
1890
|
cwd: projectDirectory,
|
|
2099
1891
|
env: npmRunPathEnv(),
|
|
2100
1892
|
stdio: "pipe"
|
|
@@ -2119,7 +1911,7 @@ function getReactRouterRoutes(projectDirectory) {
|
|
|
2119
1911
|
if (!checkReactRouterCli(projectDirectory)) throw new Error("React Router CLI is not available. Please make sure @react-router/dev is installed and accessible.");
|
|
2120
1912
|
const tempFile = join(tmpdir(), `react-router-routes-${randomUUID()}.json`);
|
|
2121
1913
|
try {
|
|
2122
|
-
execSync
|
|
1914
|
+
execSync(`react-router routes --json > "${tempFile}"`, {
|
|
2123
1915
|
cwd: projectDirectory,
|
|
2124
1916
|
env: npmRunPathEnv(),
|
|
2125
1917
|
encoding: "utf-8",
|
|
@@ -2131,8 +1923,8 @@ function getReactRouterRoutes(projectDirectory) {
|
|
|
2131
1923
|
});
|
|
2132
1924
|
const output = readFileSync$1(tempFile, "utf-8");
|
|
2133
1925
|
return JSON.parse(output);
|
|
2134
|
-
} catch (error
|
|
2135
|
-
throw new Error(`Failed to get routes from React Router CLI: ${error
|
|
1926
|
+
} catch (error) {
|
|
1927
|
+
throw new Error(`Failed to get routes from React Router CLI: ${error.message}`);
|
|
2136
1928
|
} finally {
|
|
2137
1929
|
try {
|
|
2138
1930
|
if (existsSync$1(tempFile)) unlinkSync(tempFile);
|
|
@@ -2276,8 +2068,8 @@ function parseNestedObject(objectLiteral) {
|
|
|
2276
2068
|
const initializer = property.getInitializer();
|
|
2277
2069
|
if (initializer) result[name] = parseExpression(initializer);
|
|
2278
2070
|
}
|
|
2279
|
-
} catch (error
|
|
2280
|
-
console.warn(`Warning: Could not parse nested object: ${error
|
|
2071
|
+
} catch (error) {
|
|
2072
|
+
console.warn(`Warning: Could not parse nested object: ${error.message}`);
|
|
2281
2073
|
return result;
|
|
2282
2074
|
}
|
|
2283
2075
|
return result;
|
|
@@ -2287,8 +2079,8 @@ function parseArrayLiteral(arrayLiteral) {
|
|
|
2287
2079
|
try {
|
|
2288
2080
|
const elements = arrayLiteral.getElements();
|
|
2289
2081
|
for (const element of elements) result.push(parseExpression(element));
|
|
2290
|
-
} catch (error
|
|
2291
|
-
console.warn(`Warning: Could not parse array literal: ${error
|
|
2082
|
+
} catch (error) {
|
|
2083
|
+
console.warn(`Warning: Could not parse array literal: ${error.message}`);
|
|
2292
2084
|
}
|
|
2293
2085
|
return result;
|
|
2294
2086
|
}
|
|
@@ -2320,8 +2112,8 @@ function parseDecoratorArgs(decorator) {
|
|
|
2320
2112
|
}
|
|
2321
2113
|
}
|
|
2322
2114
|
return result;
|
|
2323
|
-
} catch (error
|
|
2324
|
-
console.warn(`Warning: Could not parse decorator arguments: ${error
|
|
2115
|
+
} catch (error) {
|
|
2116
|
+
console.warn(`Warning: Could not parse decorator arguments: ${error.message}`);
|
|
2325
2117
|
return result;
|
|
2326
2118
|
}
|
|
2327
2119
|
}
|
|
@@ -2349,8 +2141,8 @@ function extractAttributesFromSource(sourceFile, className) {
|
|
|
2349
2141
|
if (config.defaultValue !== void 0) attribute.default_value = config.defaultValue;
|
|
2350
2142
|
attributes.push(attribute);
|
|
2351
2143
|
}
|
|
2352
|
-
} catch (error
|
|
2353
|
-
console.warn(`Warning: Could not extract attributes from class ${className}: ${error
|
|
2144
|
+
} catch (error) {
|
|
2145
|
+
console.warn(`Warning: Could not extract attributes from class ${className}: ${error.message}`);
|
|
2354
2146
|
}
|
|
2355
2147
|
return attributes;
|
|
2356
2148
|
}
|
|
@@ -2384,8 +2176,8 @@ function extractRegionDefinitionsFromSource(sourceFile, className) {
|
|
|
2384
2176
|
}
|
|
2385
2177
|
}
|
|
2386
2178
|
}
|
|
2387
|
-
} catch (error
|
|
2388
|
-
console.warn(`Warning: Could not extract region definitions from class ${className}: ${error
|
|
2179
|
+
} catch (error) {
|
|
2180
|
+
console.warn(`Warning: Could not extract region definitions from class ${className}: ${error.message}`);
|
|
2389
2181
|
}
|
|
2390
2182
|
return regionDefinitions;
|
|
2391
2183
|
}
|
|
@@ -2418,12 +2210,12 @@ async function processComponentFile(filePath, _projectRoot) {
|
|
|
2418
2210
|
};
|
|
2419
2211
|
components.push(componentMetadata);
|
|
2420
2212
|
}
|
|
2421
|
-
} catch (error
|
|
2422
|
-
console.warn(`Warning: Could not process file ${filePath}:`, error
|
|
2213
|
+
} catch (error) {
|
|
2214
|
+
console.warn(`Warning: Could not process file ${filePath}:`, error.message);
|
|
2423
2215
|
}
|
|
2424
2216
|
return components;
|
|
2425
|
-
} catch (error
|
|
2426
|
-
console.warn(`Warning: Could not read file ${filePath}:`, error
|
|
2217
|
+
} catch (error) {
|
|
2218
|
+
console.warn(`Warning: Could not read file ${filePath}:`, error.message);
|
|
2427
2219
|
return [];
|
|
2428
2220
|
}
|
|
2429
2221
|
}
|
|
@@ -2458,12 +2250,12 @@ async function processPageTypeFile(filePath, projectRoot) {
|
|
|
2458
2250
|
};
|
|
2459
2251
|
pageTypes.push(pageTypeMetadata);
|
|
2460
2252
|
}
|
|
2461
|
-
} catch (error
|
|
2462
|
-
console.warn(`Warning: Could not process file ${filePath}:`, error
|
|
2253
|
+
} catch (error) {
|
|
2254
|
+
console.warn(`Warning: Could not process file ${filePath}:`, error.message);
|
|
2463
2255
|
}
|
|
2464
2256
|
return pageTypes;
|
|
2465
|
-
} catch (error
|
|
2466
|
-
console.warn(`Warning: Could not read file ${filePath}:`, error
|
|
2257
|
+
} catch (error) {
|
|
2258
|
+
console.warn(`Warning: Could not read file ${filePath}:`, error.message);
|
|
2467
2259
|
return [];
|
|
2468
2260
|
}
|
|
2469
2261
|
}
|
|
@@ -2489,8 +2281,8 @@ async function processAspectFile(filePath, _projectRoot) {
|
|
|
2489
2281
|
console.warn(`Warning: Could not parse JSON in file ${filePath}:`, parseError.message);
|
|
2490
2282
|
}
|
|
2491
2283
|
return aspects;
|
|
2492
|
-
} catch (error
|
|
2493
|
-
console.warn(`Warning: Could not read file ${filePath}:`, error
|
|
2284
|
+
} catch (error) {
|
|
2285
|
+
console.warn(`Warning: Could not read file ${filePath}:`, error.message);
|
|
2494
2286
|
return [];
|
|
2495
2287
|
}
|
|
2496
2288
|
}
|
|
@@ -2567,14 +2359,14 @@ async function generateAspectCartridge(aspect, outputDir, dryRun = false) {
|
|
|
2567
2359
|
function lintGeneratedFiles(metadataDir, projectRoot) {
|
|
2568
2360
|
try {
|
|
2569
2361
|
console.log("🔧 Running ESLint --fix on generated JSON files...");
|
|
2570
|
-
execSync
|
|
2362
|
+
execSync(`npx eslint "${metadataDir}/**/*.json" --fix --no-error-on-unmatched-pattern`, {
|
|
2571
2363
|
cwd: projectRoot,
|
|
2572
2364
|
stdio: "pipe",
|
|
2573
2365
|
encoding: "utf-8"
|
|
2574
2366
|
});
|
|
2575
2367
|
console.log("✅ JSON files formatted successfully");
|
|
2576
|
-
} catch (error
|
|
2577
|
-
const execError = error
|
|
2368
|
+
} catch (error) {
|
|
2369
|
+
const execError = error;
|
|
2578
2370
|
if (execError.status === 2) {
|
|
2579
2371
|
const errMsg = execError.stderr || execError.stdout || "Unknown error";
|
|
2580
2372
|
console.warn(`⚠️ Warning: Could not run ESLint --fix: ${errMsg}`);
|
|
@@ -2620,11 +2412,11 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
|
|
|
2620
2412
|
aspectsOutputDir
|
|
2621
2413
|
]) try {
|
|
2622
2414
|
await mkdir(outputDir, { recursive: true });
|
|
2623
|
-
} catch (error
|
|
2415
|
+
} catch (error) {
|
|
2624
2416
|
try {
|
|
2625
2417
|
await access(outputDir);
|
|
2626
2418
|
} catch {
|
|
2627
|
-
console.error(`❌ Error: Failed to create output directory ${outputDir}: ${error
|
|
2419
|
+
console.error(`❌ Error: Failed to create output directory ${outputDir}: ${error.message}`);
|
|
2628
2420
|
process.exit(1);
|
|
2629
2421
|
}
|
|
2630
2422
|
}
|
|
@@ -2692,12 +2484,12 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
|
|
|
2692
2484
|
aspectsGenerated: allAspects.length,
|
|
2693
2485
|
totalFiles: allComponents.length + allPageTypes.length + allAspects.length
|
|
2694
2486
|
};
|
|
2695
|
-
} catch (error
|
|
2696
|
-
console.error("❌ Error:", error
|
|
2487
|
+
} catch (error) {
|
|
2488
|
+
console.error("❌ Error:", error.message);
|
|
2697
2489
|
process.exit(1);
|
|
2698
2490
|
}
|
|
2699
2491
|
}
|
|
2700
2492
|
|
|
2701
2493
|
//#endregion
|
|
2702
|
-
export { createServer,
|
|
2494
|
+
export { createServer, storefrontNextTargets as default, generateMetadata, loadConfigFromEnv, loadProjectConfig, transformTargetPlaceholderPlugin, trimExtensions };
|
|
2703
2495
|
//# sourceMappingURL=index.js.map
|