@salesforce/storefront-next-dev 1.0.1 → 1.1.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/dist/cartridge-services/index.d.ts.map +1 -1
- package/dist/cartridge-services/index.js +4 -0
- package/dist/cartridge-services/index.js.map +1 -1
- package/dist/commands/config/aggregate-extensions.js +279 -0
- package/dist/commands/locales/aggregate-extensions.js +3 -2
- package/dist/commands/push.js +2 -0
- package/dist/constants.js +29 -0
- package/dist/entry/server.js +8 -4
- package/dist/entry/server.js.map +1 -1
- package/dist/generate-cartridge.js +4 -0
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +150 -20
- package/dist/index.js.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/mrt/ssr.mjs +51 -51
- package/dist/mrt/ssr.mjs.map +1 -1
- package/dist/mrt/streamingHandler.mjs +57 -57
- package/dist/mrt/streamingHandler.mjs.map +1 -1
- package/dist/server.js +10 -5
- package/package.json +4 -4
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
|
-
import path from "node:path";
|
|
1
|
+
import path, { resolve } from "node:path";
|
|
2
2
|
import fs from "fs-extra";
|
|
3
3
|
import chalk from "chalk";
|
|
4
|
-
import
|
|
5
|
-
import {
|
|
4
|
+
import { createRequire } from "module";
|
|
5
|
+
import path$1, { dirname, join, relative, resolve as resolve$1 } from "path";
|
|
6
|
+
import { fileURLToPath } from "url";
|
|
6
7
|
import { parse } from "@babel/parser";
|
|
7
8
|
import { booleanLiteral, identifier, importDeclaration, importSpecifier, isArrayPattern, isClassDeclaration, isExportSpecifier, isFunctionDeclaration, isIdentifier, isJSXAttribute, isJSXElement, isJSXFragment, isJSXIdentifier, isMemberExpression, isObjectPattern, isObjectProperty, isRestElement, isStringLiteral, isVariableDeclaration, jsxAttribute, jsxClosingElement, jsxClosingFragment, jsxElement, jsxExpressionContainer, jsxFragment, jsxIdentifier, jsxOpeningElement, jsxOpeningFragment, jsxText, stringLiteral } from "@babel/types";
|
|
8
9
|
import { generate } from "@babel/generator";
|
|
@@ -10,7 +11,7 @@ import traverseModule from "@babel/traverse";
|
|
|
10
11
|
import { existsSync, readFileSync, writeFileSync } from "fs";
|
|
11
12
|
import { glob } from "glob";
|
|
12
13
|
import { Node, Project, ts } from "ts-morph";
|
|
13
|
-
import fs$1 from "node:fs";
|
|
14
|
+
import fs$1, { existsSync as existsSync$1, readFileSync as readFileSync$1 } from "node:fs";
|
|
14
15
|
import { deadCodeElimination, findReferencedIdentifiers } from "babel-dead-code-elimination";
|
|
15
16
|
import httpProxy from "http-proxy";
|
|
16
17
|
import { brotliDecompressSync, gunzipSync, inflateSync } from "zlib";
|
|
@@ -605,6 +606,45 @@ function buildTargetRegistry(rootDir, options = {}) {
|
|
|
605
606
|
actionHookRegistry
|
|
606
607
|
};
|
|
607
608
|
}
|
|
609
|
+
const TARGET_ID_PATTERN = /<UITarget[\s][^>]*targetId=["']([^"']+)["']/g;
|
|
610
|
+
const EXCLUDED_DIRS = new Set(["ui-target-dev-mode", "ui-target-smoke-test"]);
|
|
611
|
+
/**
|
|
612
|
+
* Recursively collect all UITarget IDs declared in template source files.
|
|
613
|
+
* Excludes extension directories so only "real" UITarget placements are counted.
|
|
614
|
+
*/
|
|
615
|
+
function collectUITargetIds(sourceDir) {
|
|
616
|
+
const result = /* @__PURE__ */ new Set();
|
|
617
|
+
function walk(dir) {
|
|
618
|
+
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
619
|
+
for (const entry of entries) {
|
|
620
|
+
const fullPath = path$1.join(dir, entry.name);
|
|
621
|
+
if (entry.isDirectory()) {
|
|
622
|
+
if (entry.name === "extensions" || EXCLUDED_DIRS.has(entry.name)) continue;
|
|
623
|
+
walk(fullPath);
|
|
624
|
+
} else if (entry.isFile() && /\.(tsx?|jsx?)$/.test(entry.name) && !/\.test\.(tsx?|jsx?)$/.test(entry.name)) {
|
|
625
|
+
const content = fs.readFileSync(fullPath, "utf-8");
|
|
626
|
+
TARGET_ID_PATTERN.lastIndex = 0;
|
|
627
|
+
let match;
|
|
628
|
+
while ((match = TARGET_ID_PATTERN.exec(content)) !== null) result.add(match[1]);
|
|
629
|
+
}
|
|
630
|
+
}
|
|
631
|
+
}
|
|
632
|
+
walk(sourceDir);
|
|
633
|
+
return result;
|
|
634
|
+
}
|
|
635
|
+
/**
|
|
636
|
+
* Validate that all targetIds in the component registry correspond to
|
|
637
|
+
* UITarget declarations in the template source. Returns orphaned entries.
|
|
638
|
+
*/
|
|
639
|
+
function validateTargetRegistry(componentRegistry, declaredTargetIds) {
|
|
640
|
+
const orphaned = [];
|
|
641
|
+
for (const targetId in componentRegistry) if (!declaredTargetIds.has(targetId)) for (const entry of componentRegistry[targetId]) orphaned.push({
|
|
642
|
+
targetId,
|
|
643
|
+
extension: entry.namespace,
|
|
644
|
+
componentPath: entry.path
|
|
645
|
+
});
|
|
646
|
+
return orphaned;
|
|
647
|
+
}
|
|
608
648
|
|
|
609
649
|
//#endregion
|
|
610
650
|
//#region src/plugins/transformTargets.ts
|
|
@@ -622,6 +662,12 @@ function transformTargetPlaceholderPlugin() {
|
|
|
622
662
|
},
|
|
623
663
|
buildStart() {
|
|
624
664
|
({componentRegistry, contextProviders} = buildTargetRegistry(sourceDir, { isProduction }));
|
|
665
|
+
const declaredTargetIds = collectUITargetIds(sourceDir);
|
|
666
|
+
const orphaned = validateTargetRegistry(componentRegistry, declaredTargetIds);
|
|
667
|
+
if (orphaned.length > 0) {
|
|
668
|
+
const lines = orphaned.map((o) => ` • "${o.targetId}" (extension: ${o.extension}, component: ${o.componentPath})`);
|
|
669
|
+
throw new Error(`[storefront-next] ${orphaned.length} extension component(s) target UITarget IDs that do not exist in the template:\n${lines.join("\n")}\n\nEither add a <UITarget targetId="..."> to the template or remove/disable the component in target-config.json.`);
|
|
670
|
+
}
|
|
625
671
|
},
|
|
626
672
|
transform(code, id) {
|
|
627
673
|
if (process.env.VITE_UI_TARGET_DEV_MODE === "true") return null;
|
|
@@ -833,7 +879,7 @@ async function scanComponents(project, projectRoot, componentPath, registryPath)
|
|
|
833
879
|
});
|
|
834
880
|
logger.debug(`🔍 Scanning ${componentFiles.length} files in ${componentPath}...`);
|
|
835
881
|
const components = [];
|
|
836
|
-
const registryDir = dirname(resolve(projectRoot, registryPath));
|
|
882
|
+
const registryDir = dirname(resolve$1(projectRoot, registryPath));
|
|
837
883
|
for (const filePath of componentFiles) try {
|
|
838
884
|
const content = readFileSync(filePath, "utf-8");
|
|
839
885
|
const sourceFile = project.createSourceFile(filePath, content, { overwrite: true });
|
|
@@ -876,11 +922,10 @@ async function scanComponents(project, projectRoot, componentPath, registryPath)
|
|
|
876
922
|
function generateRegistryCode(components, registryIdentifier = "registry") {
|
|
877
923
|
const sorted = [...components].sort((a, b) => a.id.localeCompare(b.id) || a.relativePath.localeCompare(b.relativePath));
|
|
878
924
|
if (sorted.length === 0) return `
|
|
879
|
-
/* eslint-disable */
|
|
880
925
|
/**
|
|
881
926
|
* Initialize the static component registry.
|
|
882
927
|
* This function is auto-generated by the staticRegistry Vite plugin.
|
|
883
|
-
*
|
|
928
|
+
*
|
|
884
929
|
* DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.
|
|
885
930
|
*/
|
|
886
931
|
export function initializeRegistry(targetRegistry = ${registryIdentifier}): void {
|
|
@@ -897,13 +942,12 @@ export function initializeRegistry(targetRegistry = ${registryIdentifier}): void
|
|
|
897
942
|
} else return ` targetRegistry.registerImporter('${id}', () => import('${relativePath}'));`;
|
|
898
943
|
}).join("\n");
|
|
899
944
|
return `
|
|
900
|
-
/* eslint-disable */
|
|
901
945
|
/**
|
|
902
946
|
* Initialize the static component registry.
|
|
903
947
|
* This function is auto-generated by the staticRegistry Vite plugin.
|
|
904
|
-
*
|
|
948
|
+
*
|
|
905
949
|
* DO NOT EDIT THIS FUNCTION MANUALLY - it will be overwritten on next build.
|
|
906
|
-
*
|
|
950
|
+
*
|
|
907
951
|
* Components registered: ${sorted.map((c) => c.id).join(", ")}
|
|
908
952
|
*/
|
|
909
953
|
export function initializeRegistry(targetRegistry = ${registryIdentifier}): void {
|
|
@@ -912,9 +956,42 @@ ${registrations}
|
|
|
912
956
|
`;
|
|
913
957
|
}
|
|
914
958
|
/**
|
|
959
|
+
* Formats registry file content with the project's own Prettier so the written file
|
|
960
|
+
* matches what the project's formatter would produce.
|
|
961
|
+
*
|
|
962
|
+
* Without this, a standalone `prettier --write` (pre-commit hook, format-on-save) rewrites
|
|
963
|
+
* the generated file on every commit: the generator emits one `registerImporter` call per
|
|
964
|
+
* line and cannot anticipate an arbitrary `printWidth`, so Prettier re-wraps long lines and
|
|
965
|
+
* the regenerate -> format -> regenerate loop never settles.
|
|
966
|
+
*
|
|
967
|
+
* Prettier is resolved from the registry file's own location — i.e. the consuming project's
|
|
968
|
+
* `node_modules`, not the SDK bundle (which keeps `node_modules` external). It is the project's
|
|
969
|
+
* Prettier version and config whose output must be matched. Returns the content unchanged when
|
|
970
|
+
* Prettier is absent or fails, so registry generation never breaks a build over formatting.
|
|
971
|
+
*/
|
|
972
|
+
let warnedNoPrettier = false;
|
|
973
|
+
async function formatWithProjectPrettier(content, registryFilePath) {
|
|
974
|
+
try {
|
|
975
|
+
const prettier = createRequire(registryFilePath)("prettier");
|
|
976
|
+
const config = await prettier.resolveConfig(registryFilePath, { editorconfig: true });
|
|
977
|
+
return await prettier.format(content, {
|
|
978
|
+
...config,
|
|
979
|
+
filepath: registryFilePath
|
|
980
|
+
});
|
|
981
|
+
} catch (error) {
|
|
982
|
+
if (error.code === "MODULE_NOT_FOUND") {
|
|
983
|
+
if (!warnedNoPrettier) {
|
|
984
|
+
logger.warn("⚠️ Prettier not found in the project; static registry will be written unformatted.");
|
|
985
|
+
warnedNoPrettier = true;
|
|
986
|
+
}
|
|
987
|
+
} else logger.warn(`⚠️ Skipping Prettier formatting for registry file: ${error.message}`);
|
|
988
|
+
return content;
|
|
989
|
+
}
|
|
990
|
+
}
|
|
991
|
+
/**
|
|
915
992
|
* Updates the registry.ts file with the generated code
|
|
916
993
|
*/
|
|
917
|
-
function updateRegistryFile(registryFilePath, generatedCode) {
|
|
994
|
+
async function updateRegistryFile(registryFilePath, generatedCode) {
|
|
918
995
|
let existingContent;
|
|
919
996
|
if (!existsSync(registryFilePath)) {
|
|
920
997
|
logger.debug("📝 Creating new registry file...");
|
|
@@ -939,7 +1016,7 @@ export const registry = new ComponentRegistry();
|
|
|
939
1016
|
const startIndex = existingContent.indexOf(startMarker);
|
|
940
1017
|
const endIndex = existingContent.indexOf(endMarker);
|
|
941
1018
|
if (startIndex === -1 || endIndex === -1) throw new Error(`Registry file ${registryFilePath} is missing static registry markers. Please add "${startMarker}" and "${endMarker}" markers to define the generated content area.`);
|
|
942
|
-
const updatedContent = `${existingContent.slice(0, startIndex + 24)}\n${generatedCode}\n${existingContent.slice(endIndex)}
|
|
1019
|
+
const updatedContent = await formatWithProjectPrettier(`${existingContent.slice(0, startIndex + 24)}\n${generatedCode}\n${existingContent.slice(endIndex)}`, registryFilePath);
|
|
943
1020
|
if (updatedContent === existingContent) {
|
|
944
1021
|
logger.debug(`⏭️ Registry unchanged, skipping write: ${registryFilePath}`);
|
|
945
1022
|
return false;
|
|
@@ -990,8 +1067,8 @@ const staticRegistryPlugin = (config = {}) => {
|
|
|
990
1067
|
} }), projectRoot, componentPath, registryPath);
|
|
991
1068
|
logger.debug(`📦 Found ${components.length} components with @Component decorators`);
|
|
992
1069
|
const generatedCode = generateRegistryCode(components, registryIdentifier);
|
|
993
|
-
const registryFilePath = resolve(projectRoot, registryPath);
|
|
994
|
-
const changed = updateRegistryFile(registryFilePath, generatedCode);
|
|
1070
|
+
const registryFilePath = resolve$1(projectRoot, registryPath);
|
|
1071
|
+
const changed = await updateRegistryFile(registryFilePath, generatedCode);
|
|
995
1072
|
logger.debug("✅ Static registry generation complete!");
|
|
996
1073
|
return {
|
|
997
1074
|
registryFilePath,
|
|
@@ -1033,15 +1110,68 @@ const staticRegistryPlugin = (config = {}) => {
|
|
|
1033
1110
|
};
|
|
1034
1111
|
};
|
|
1035
1112
|
|
|
1113
|
+
//#endregion
|
|
1114
|
+
//#region src/server/ts-import.ts
|
|
1115
|
+
/**
|
|
1116
|
+
* Parse TypeScript paths from tsconfig.json and convert to jiti alias format.
|
|
1117
|
+
*
|
|
1118
|
+
* @param tsconfigPath - Path to tsconfig.json
|
|
1119
|
+
* @param projectDirectory - Project root directory for resolving relative paths
|
|
1120
|
+
* @returns Record of alias mappings for jiti
|
|
1121
|
+
*
|
|
1122
|
+
* @example
|
|
1123
|
+
* // tsconfig.json: { "compilerOptions": { "paths": { "@/*": ["./src/*"] } } }
|
|
1124
|
+
* // Returns: { "@/": "/absolute/path/to/src/" }
|
|
1125
|
+
*/
|
|
1126
|
+
function parseTsconfigPaths(tsconfigPath, projectDirectory) {
|
|
1127
|
+
const alias = {};
|
|
1128
|
+
if (!existsSync$1(tsconfigPath)) return alias;
|
|
1129
|
+
try {
|
|
1130
|
+
const tsconfigContent = readFileSync$1(tsconfigPath, "utf-8");
|
|
1131
|
+
const tsconfig = JSON.parse(tsconfigContent);
|
|
1132
|
+
const paths = tsconfig.compilerOptions?.paths;
|
|
1133
|
+
const baseUrl = tsconfig.compilerOptions?.baseUrl || ".";
|
|
1134
|
+
if (paths) {
|
|
1135
|
+
for (const [key, values] of Object.entries(paths)) if (values && values.length > 0) {
|
|
1136
|
+
const aliasKey = key.replace(/\/\*$/, "/");
|
|
1137
|
+
alias[aliasKey] = resolve(projectDirectory, baseUrl, values[0].replace(/\/\*$/, "/").replace(/^\.\//, ""));
|
|
1138
|
+
}
|
|
1139
|
+
}
|
|
1140
|
+
} catch {}
|
|
1141
|
+
const sortedAlias = {};
|
|
1142
|
+
Object.keys(alias).sort((a, b) => b.length - a.length).forEach((key) => {
|
|
1143
|
+
sortedAlias[key] = alias[key];
|
|
1144
|
+
});
|
|
1145
|
+
return sortedAlias;
|
|
1146
|
+
}
|
|
1147
|
+
/**
|
|
1148
|
+
* Import a TypeScript file using jiti with proper path alias resolution.
|
|
1149
|
+
* This is a cross-platform alternative to tsx that works on Windows.
|
|
1150
|
+
*
|
|
1151
|
+
* @param filePath - Absolute path to the TypeScript file to import
|
|
1152
|
+
* @param options - Import options including project directory
|
|
1153
|
+
* @returns The imported module
|
|
1154
|
+
*/
|
|
1155
|
+
async function importTypescript(filePath, options) {
|
|
1156
|
+
const { projectDirectory, tsconfigPath = resolve(projectDirectory, "tsconfig.json") } = options;
|
|
1157
|
+
const { createJiti } = await import("jiti");
|
|
1158
|
+
const alias = parseTsconfigPaths(tsconfigPath, projectDirectory);
|
|
1159
|
+
return createJiti(import.meta.url, {
|
|
1160
|
+
fsCache: false,
|
|
1161
|
+
interopDefault: true,
|
|
1162
|
+
alias
|
|
1163
|
+
}).import(filePath);
|
|
1164
|
+
}
|
|
1165
|
+
|
|
1036
1166
|
//#endregion
|
|
1037
1167
|
//#region src/plugins/configLoader.ts
|
|
1038
1168
|
/**
|
|
1039
1169
|
* Load the engagement config from config.server.ts
|
|
1040
1170
|
*/
|
|
1041
1171
|
async function loadEngagementConfig(projectRoot, configPath) {
|
|
1042
|
-
const absoluteConfigPath = resolve(projectRoot, configPath);
|
|
1172
|
+
const absoluteConfigPath = resolve$1(projectRoot, configPath);
|
|
1043
1173
|
try {
|
|
1044
|
-
const config = (await
|
|
1174
|
+
const config = (await importTypescript(absoluteConfigPath, { projectDirectory: projectRoot })).default;
|
|
1045
1175
|
logger.debug(`📄 Loaded config from ${configPath}`);
|
|
1046
1176
|
const engagement = config?.app?.engagement;
|
|
1047
1177
|
if (!engagement) {
|
|
@@ -1066,7 +1196,7 @@ async function scanForInstrumentedEvents(projectRoot, scanPaths) {
|
|
|
1066
1196
|
const sendViewPagePattern = /sendViewPageEvent\s*\(/g;
|
|
1067
1197
|
const createEventPattern = /createEvent\s*\(\s*['"]([^'"]+)['"]/g;
|
|
1068
1198
|
for (const scanPath of scanPaths) {
|
|
1069
|
-
const files = await glob(join(resolve(projectRoot, scanPath), "**/*.{ts,tsx}"), { ignore: [
|
|
1199
|
+
const files = await glob(join(resolve$1(projectRoot, scanPath), "**/*.{ts,tsx}"), { ignore: [
|
|
1070
1200
|
"**/*.test.ts",
|
|
1071
1201
|
"**/*.test.tsx",
|
|
1072
1202
|
"**/*.spec.ts",
|
|
@@ -1218,17 +1348,17 @@ const buildMiddlewareRegistryPlugin = () => {
|
|
|
1218
1348
|
configResolved(config) {
|
|
1219
1349
|
resolvedConfig = config;
|
|
1220
1350
|
const rr = config.__reactRouterPluginContext?.reactRouterConfig ?? {};
|
|
1221
|
-
buildDirectory = rr.buildDirectory ?? resolve(config.root, "build");
|
|
1351
|
+
buildDirectory = rr.buildDirectory ?? resolve$1(config.root, "build");
|
|
1222
1352
|
appDirectory = rr.appDirectory ?? "src";
|
|
1223
1353
|
},
|
|
1224
1354
|
buildApp: {
|
|
1225
1355
|
order: "post",
|
|
1226
1356
|
handler: async () => {
|
|
1227
1357
|
const projectRoot = resolvedConfig.root;
|
|
1228
|
-
const middlewareRegistryPath = resolve(projectRoot, appDirectory, SERVER_OUT_SUBDIR, MIDDLEWARE_REGISTRY_SOURCE_FILE);
|
|
1358
|
+
const middlewareRegistryPath = resolve$1(projectRoot, appDirectory, SERVER_OUT_SUBDIR, MIDDLEWARE_REGISTRY_SOURCE_FILE);
|
|
1229
1359
|
if (!existsSync(middlewareRegistryPath)) return;
|
|
1230
1360
|
const { build } = await import("tsdown");
|
|
1231
|
-
const serverOutDir = resolve(projectRoot, buildDirectory, SERVER_OUT_SUBDIR);
|
|
1361
|
+
const serverOutDir = resolve$1(projectRoot, buildDirectory, SERVER_OUT_SUBDIR);
|
|
1232
1362
|
await build({
|
|
1233
1363
|
cwd: projectRoot,
|
|
1234
1364
|
entry: { [MIDDLEWARE_REGISTRY_SOURCE_FILE.replace(/\.ts$/, "")]: middlewareRegistryPath },
|