@salesforce/storefront-next-dev 1.1.0 → 1.2.0-alpha.1
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 +1 -1
- package/dist/cartridge-services/index.d.ts +1 -1
- package/dist/cartridge-services/index.d.ts.map +1 -1
- package/dist/cartridge-services/index.js +134 -23
- package/dist/cartridge-services/index.js.map +1 -1
- package/dist/commands/create-storefront.js +20 -1
- package/dist/commands/extensions/create.js +1 -0
- package/dist/commands/extensions/install.js +1 -0
- package/dist/commands/extensions/list.js +1 -0
- package/dist/commands/extensions/remove.js +1 -0
- package/dist/commands/generate-cartridge.js +1 -0
- package/dist/commands/push.js +1 -0
- package/dist/commands/scapi/available.js +6 -0
- package/dist/configs/react-router.config.d.ts +5 -1
- package/dist/configs/react-router.config.d.ts.map +1 -1
- package/dist/configs/react-router.config.js +6 -2
- package/dist/configs/react-router.config.js.map +1 -1
- package/dist/dependency-utils.js +4 -54
- package/dist/entry/server.js +49 -13
- package/dist/entry/server.js.map +1 -1
- package/dist/format-with-project-biome.js +108 -0
- package/dist/generate-cartridge.js +78 -23
- package/dist/generate-custom-clients.js +1 -0
- package/dist/index.js +79 -45
- package/dist/index.js.map +1 -1
- package/dist/logger/index.js.map +1 -1
- package/dist/logger.js +1 -1
- package/dist/mrt/ssr.mjs +6 -8
- package/dist/mrt/ssr.mjs.map +1 -1
- package/dist/mrt/streamingHandler.mjs +22 -24
- package/dist/mrt/streamingHandler.mjs.map +1 -1
- package/dist/react-router/Scripts.js.map +1 -1
- package/dist/server.js +2 -2
- package/package.json +11 -9
package/README.md
CHANGED
|
@@ -102,7 +102,7 @@ pnpm sfnext create-instructions --project-directory /path/to/your/project --exte
|
|
|
102
102
|
|
|
103
103
|
#### Cartridge generation and deployment instructions
|
|
104
104
|
|
|
105
|
-
Run these commands from your project directory
|
|
105
|
+
Run these commands from your project directory:
|
|
106
106
|
|
|
107
107
|
```bash
|
|
108
108
|
# Generate cartridge metadata for your site
|
|
@@ -11,7 +11,7 @@ interface GenerateMetadataOptions {
|
|
|
11
11
|
*/
|
|
12
12
|
filePaths?: string[];
|
|
13
13
|
/**
|
|
14
|
-
* Whether to
|
|
14
|
+
* Whether to format generated JSON files with Biome so they match the project's formatting.
|
|
15
15
|
* Defaults to true.
|
|
16
16
|
*/
|
|
17
17
|
lintFix?: boolean;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cartridge-services/generate-cartridge.ts"],"sourcesContent":[],"mappings":";;
|
|
1
|
+
{"version":3,"file":"index.d.ts","names":[],"sources":["../../src/cartridge-services/generate-cartridge.ts"],"sourcesContent":[],"mappings":";;AAk8BA;;;AAIG,UA/Cc,uBAAA,CA+Cd;EAAO;;;;;;;;;;;;;;;;;;;;UAvBO,sBAAA;;;;;;iBAmBK,gBAAA,gEAGR,0BACX,QAAQ"}
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
import { access, mkdir, readFile, readdir, rm, writeFile } from "node:fs/promises";
|
|
2
2
|
import { basename, extname, join, resolve } from "node:path";
|
|
3
|
-
import { execSync } from "node:child_process";
|
|
4
3
|
import { Node, Project } from "ts-morph";
|
|
5
4
|
import { existsSync, readFileSync, unlinkSync } from "node:fs";
|
|
5
|
+
import { execSync } from "node:child_process";
|
|
6
6
|
import { tmpdir } from "node:os";
|
|
7
7
|
import { randomUUID } from "node:crypto";
|
|
8
8
|
import { npmRunPathEnv } from "npm-run-path";
|
|
9
9
|
import chalk from "chalk";
|
|
10
|
+
import { createRequire } from "module";
|
|
11
|
+
import { spawnSync } from "child_process";
|
|
12
|
+
import { dirname, join as join$1 } from "path";
|
|
13
|
+
import { fileURLToPath } from "url";
|
|
10
14
|
|
|
11
15
|
//#region src/utils/logger.ts
|
|
12
16
|
const LEVEL_PRIORITY = {
|
|
@@ -174,6 +178,59 @@ function flattenRoutes(routes, parentPath = "") {
|
|
|
174
178
|
return result;
|
|
175
179
|
}
|
|
176
180
|
|
|
181
|
+
//#endregion
|
|
182
|
+
//#region src/utils/format-with-project-biome.ts
|
|
183
|
+
/** This module's own path — the resolution root for the SDK-bundled Biome fallback. */
|
|
184
|
+
const HERE = fileURLToPath(import.meta.url);
|
|
185
|
+
/**
|
|
186
|
+
* Format every file Biome recognizes under `directory` in place with the consuming project's
|
|
187
|
+
* Biome, so generated files (e.g. cartridge metadata JSON) match `biome format` / `pnpm lint:fix`.
|
|
188
|
+
*
|
|
189
|
+
* Unlike {@link formatWithProjectBiome} (single file via stdin), this runs `biome format --write`
|
|
190
|
+
* against the directory so Biome discovers the project's `biome.json` by walking up from it and
|
|
191
|
+
* applies the project's formatting to the written files directly.
|
|
192
|
+
*
|
|
193
|
+
* Fail-safe: when Biome can't be resolved or the run fails, the files are left as written (valid,
|
|
194
|
+
* just unformatted) and a warning is logged — generation never breaks over formatting.
|
|
195
|
+
*
|
|
196
|
+
* @param directory - Directory whose Biome-recognized files should be formatted in place.
|
|
197
|
+
*/
|
|
198
|
+
function formatDirectoryWithProjectBiome(directory) {
|
|
199
|
+
const biomeBin = resolveBiomeBin(join$1(directory, "biome-resolution-root.js"));
|
|
200
|
+
if (!biomeBin) {
|
|
201
|
+
logger.warn(`⚠️ Biome could not be resolved; generated files in ${directory} were left unformatted.`);
|
|
202
|
+
return;
|
|
203
|
+
}
|
|
204
|
+
const result = spawnSync(process.execPath, [
|
|
205
|
+
biomeBin,
|
|
206
|
+
"format",
|
|
207
|
+
"--write",
|
|
208
|
+
"."
|
|
209
|
+
], {
|
|
210
|
+
cwd: directory,
|
|
211
|
+
encoding: "utf8"
|
|
212
|
+
});
|
|
213
|
+
if (result.status !== 0) {
|
|
214
|
+
const detail = result.stderr?.trim() || `exit code ${result.status}`;
|
|
215
|
+
logger.warn(`⚠️ Some generated files in ${directory} could not be formatted by Biome: ${detail}`);
|
|
216
|
+
return;
|
|
217
|
+
}
|
|
218
|
+
logger.debug(`✅ Formatted generated files in ${directory} with Biome`);
|
|
219
|
+
}
|
|
220
|
+
/**
|
|
221
|
+
* Resolve a Biome CLI binary path, preferring the consuming project's install and falling back
|
|
222
|
+
* to the SDK-bundled copy (available pre-install). Returns null when neither resolves.
|
|
223
|
+
*/
|
|
224
|
+
function resolveBiomeBin(filePath) {
|
|
225
|
+
for (const fromPath of [filePath, HERE]) try {
|
|
226
|
+
const req = createRequire(fromPath);
|
|
227
|
+
const biomePkgJson = req.resolve("@biomejs/biome/package.json");
|
|
228
|
+
const { bin } = req(biomePkgJson);
|
|
229
|
+
return join$1(dirname(biomePkgJson), bin.biome);
|
|
230
|
+
} catch {}
|
|
231
|
+
return null;
|
|
232
|
+
}
|
|
233
|
+
|
|
177
234
|
//#endregion
|
|
178
235
|
//#region src/cartridge-services/generate-cartridge.ts
|
|
179
236
|
const SKIP_DIRECTORIES = [
|
|
@@ -224,6 +281,62 @@ function resolveAttributeType(decoratorType, tsMorphType, fieldName) {
|
|
|
224
281
|
if (tsMorphType && TYPE_MAPPING[tsMorphType]) return TYPE_MAPPING[tsMorphType];
|
|
225
282
|
return "string";
|
|
226
283
|
}
|
|
284
|
+
const SEARCHING_FORBIDDEN_TYPES = new Set([
|
|
285
|
+
"integer",
|
|
286
|
+
"boolean",
|
|
287
|
+
"file",
|
|
288
|
+
"page",
|
|
289
|
+
"image",
|
|
290
|
+
"url",
|
|
291
|
+
"enum"
|
|
292
|
+
]);
|
|
293
|
+
/**
|
|
294
|
+
* Validate an authored `searching` config against the resolved attribute type and map it to the
|
|
295
|
+
* snake_case schema shape. The metadefinition schema gates `searching` by type; an invalid
|
|
296
|
+
* combination fails cartridge generation (matching the strict behavior of `resolveAttributeType`).
|
|
297
|
+
*
|
|
298
|
+
* Rules (from attributedefinition.json):
|
|
299
|
+
* - Forbidden entirely on integer/boolean/file/page/image/url/enum.
|
|
300
|
+
* - `markup`: `sortable` must be false.
|
|
301
|
+
* - `custom`/`cms_record`: `refinable` must be false; `boost_factor`/`sortable` not allowed.
|
|
302
|
+
* - `string`/`text`/`product`/`category`: all fields allowed.
|
|
303
|
+
*/
|
|
304
|
+
function mapAttributeSearching(searching, resolvedType, fieldName) {
|
|
305
|
+
const fail = (reason) => {
|
|
306
|
+
logger.error(`Invalid 'searching' config for attribute '${fieldName}' (type '${resolvedType}'): ${reason}`);
|
|
307
|
+
process.exit(1);
|
|
308
|
+
};
|
|
309
|
+
if (SEARCHING_FORBIDDEN_TYPES.has(resolvedType)) fail(`the '${resolvedType}' attribute type does not support searching.`);
|
|
310
|
+
const { searchable, refinable, boostFactor, sortable } = searching;
|
|
311
|
+
if (typeof searchable !== "boolean" || typeof refinable !== "boolean") fail(`'searchable' and 'refinable' are required booleans.`);
|
|
312
|
+
if (resolvedType === "markup" && sortable === true) fail(`'sortable' must be false for markup attributes.`);
|
|
313
|
+
if (resolvedType === "custom" || resolvedType === "cms_record") {
|
|
314
|
+
if (refinable === true) fail(`'refinable' must be false for ${resolvedType} attributes.`);
|
|
315
|
+
if (boostFactor !== void 0) fail(`'boostFactor' is not supported for ${resolvedType} attributes.`);
|
|
316
|
+
if (sortable === true) fail(`'sortable' must be false for ${resolvedType} attributes.`);
|
|
317
|
+
}
|
|
318
|
+
const mapped = {
|
|
319
|
+
searchable,
|
|
320
|
+
refinable
|
|
321
|
+
};
|
|
322
|
+
if (boostFactor !== void 0) mapped.boost_factor = boostFactor;
|
|
323
|
+
if (sortable !== void 0) mapped.sortable = sortable;
|
|
324
|
+
return mapped;
|
|
325
|
+
}
|
|
326
|
+
/**
|
|
327
|
+
* Validate an authored `dynamicLookup` config and map it to the snake_case schema shape.
|
|
328
|
+
* The metadefinition schema allows `dynamic_lookup` on all attribute types but requires a
|
|
329
|
+
* single `aspect_attribute_alias` field (`additionalProperties: false`); a missing alias fails
|
|
330
|
+
* cartridge generation (matching the strict behavior of `resolveAttributeType`).
|
|
331
|
+
*/
|
|
332
|
+
function mapAttributeDynamicLookup(dynamicLookup, fieldName) {
|
|
333
|
+
const { aspectAttributeAlias } = dynamicLookup;
|
|
334
|
+
if (typeof aspectAttributeAlias !== "string" || aspectAttributeAlias.length === 0) {
|
|
335
|
+
logger.error(`Invalid 'dynamicLookup' config for attribute '${fieldName}': 'aspectAttributeAlias' is required and must be a non-empty string.`);
|
|
336
|
+
process.exit(1);
|
|
337
|
+
}
|
|
338
|
+
return { aspect_attribute_alias: aspectAttributeAlias };
|
|
339
|
+
}
|
|
227
340
|
function toHumanReadableName(fieldName) {
|
|
228
341
|
return fieldName.replace(/([A-Z])/g, " $1").replace(/^./, (str) => str.toUpperCase()).trim();
|
|
229
342
|
}
|
|
@@ -375,6 +488,8 @@ function extractAttributesFromSource(sourceFile, className) {
|
|
|
375
488
|
if (config.values) attribute.values = config.values;
|
|
376
489
|
if (config.defaultValue !== void 0) attribute.default_value = config.defaultValue;
|
|
377
490
|
if (config.editorDefinition !== void 0) attribute.editor_definition = config.editorDefinition;
|
|
491
|
+
if (config.searching !== void 0) attribute.searching = mapAttributeSearching(config.searching, attribute.type, String(attribute.id));
|
|
492
|
+
if (config.dynamicLookup !== void 0) attribute.dynamic_lookup = mapAttributeDynamicLookup(config.dynamicLookup, String(attribute.id));
|
|
378
493
|
attributes.push(attribute);
|
|
379
494
|
}
|
|
380
495
|
} catch (error) {
|
|
@@ -404,13 +519,19 @@ function extractRegionDefinitionsFromSource(sourceFile, className, defaultCompon
|
|
|
404
519
|
id: regionConfig.id || "region",
|
|
405
520
|
name: regionConfig.name || "Region"
|
|
406
521
|
};
|
|
522
|
+
if (regionConfig.description !== void 0) regionDefinition.description = regionConfig.description;
|
|
407
523
|
if (regionConfig.componentTypes) regionDefinition.component_types = regionConfig.componentTypes;
|
|
408
524
|
if (Array.isArray(regionConfig.componentTypeInclusions)) regionDefinition.component_type_inclusions = regionConfig.componentTypeInclusions.map((incl) => ({ type_id: normalizeComponentTypeId(String(incl), defaultComponentGroup) }));
|
|
409
525
|
if (Array.isArray(regionConfig.componentTypeExclusions)) regionDefinition.component_type_exclusions = regionConfig.componentTypeExclusions.map((excl) => ({ type_id: normalizeComponentTypeId(String(excl), defaultComponentGroup) }));
|
|
410
526
|
if (regionConfig.maxComponents !== void 0) regionDefinition.max_components = regionConfig.maxComponents;
|
|
411
527
|
if (regionConfig.minComponents !== void 0) regionDefinition.min_components = regionConfig.minComponents;
|
|
412
528
|
if (regionConfig.allowMultiple !== void 0) regionDefinition.allow_multiple = regionConfig.allowMultiple;
|
|
413
|
-
if (regionConfig.defaultComponentConstructors) regionDefinition.default_component_constructors = regionConfig.defaultComponentConstructors
|
|
529
|
+
if (Array.isArray(regionConfig.defaultComponentConstructors)) regionDefinition.default_component_constructors = regionConfig.defaultComponentConstructors.map((constructor) => {
|
|
530
|
+
const { typeId, name } = constructor;
|
|
531
|
+
const mapped = { type_id: normalizeComponentTypeId(String(typeId), defaultComponentGroup) };
|
|
532
|
+
if (name !== void 0) mapped.name = name;
|
|
533
|
+
return mapped;
|
|
534
|
+
});
|
|
414
535
|
regionDefinitions.push(regionDefinition);
|
|
415
536
|
}
|
|
416
537
|
}
|
|
@@ -491,7 +612,8 @@ async function processPageTypeFile(filePath, projectRoot) {
|
|
|
491
612
|
regionDefinitions,
|
|
492
613
|
supportedAspectTypes: pageTypeConfig.supportedAspectTypes || [],
|
|
493
614
|
attributes,
|
|
494
|
-
route
|
|
615
|
+
route,
|
|
616
|
+
...pageTypeConfig.preview !== void 0 && { preview: pageTypeConfig.preview }
|
|
495
617
|
};
|
|
496
618
|
pageTypes.push(pageTypeMetadata);
|
|
497
619
|
}
|
|
@@ -578,6 +700,7 @@ async function generatePageTypeCartridge(pageType, outputDir, dryRun = false) {
|
|
|
578
700
|
}];
|
|
579
701
|
if (pageType.supportedAspectTypes) cartridgeData.supported_aspect_types = pageType.supportedAspectTypes;
|
|
580
702
|
if (pageType.route) cartridgeData.route = pageType.route;
|
|
703
|
+
if (pageType.preview !== void 0) cartridgeData.preview = pageType.preview;
|
|
581
704
|
await writeFile(outputPath, JSON.stringify(cartridgeData, null, 2));
|
|
582
705
|
}
|
|
583
706
|
const prefix = dryRun ? " - [DRY RUN]" : " -";
|
|
@@ -600,26 +723,14 @@ async function generateAspectCartridge(aspect, outputDir, dryRun = false) {
|
|
|
600
723
|
logger.debug(`${prefix} ${String(aspect.name)}: ${String(aspect.description)} (${String(aspect.attributeDefinitions.length)} attributes) → ${fileName}.json`);
|
|
601
724
|
}
|
|
602
725
|
/**
|
|
603
|
-
*
|
|
604
|
-
*
|
|
726
|
+
* Formats the generated JSON files in place so they match the project's `biome format` output
|
|
727
|
+
* (and therefore pass `pnpm lint` out of the box). Delegates to the shared Biome helper, which
|
|
728
|
+
* prefers the consuming project's Biome and is fail-safe (logs and leaves files valid-but-
|
|
729
|
+
* unformatted rather than throwing) when Biome can't run.
|
|
605
730
|
*/
|
|
606
|
-
function lintGeneratedFiles(metadataDir
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
execSync(`npx eslint "${metadataDir}/**/*.json" --fix --no-error-on-unmatched-pattern`, {
|
|
610
|
-
cwd: projectRoot,
|
|
611
|
-
stdio: "pipe",
|
|
612
|
-
encoding: "utf-8"
|
|
613
|
-
});
|
|
614
|
-
logger.debug("✅ JSON files formatted successfully");
|
|
615
|
-
} catch (error) {
|
|
616
|
-
const execError = error;
|
|
617
|
-
if (execError.status === 2) {
|
|
618
|
-
const errMsg = execError.stderr || execError.stdout || "Unknown error";
|
|
619
|
-
logger.warn(`⚠️ Could not run ESLint --fix: ${errMsg}`);
|
|
620
|
-
} else if (execError.stderr && execError.stderr.includes("error")) logger.warn(`⚠️ Some linting issues could not be auto-fixed. Run ESLint manually to review.`);
|
|
621
|
-
else logger.debug("✅ JSON files formatted successfully");
|
|
622
|
-
}
|
|
731
|
+
function lintGeneratedFiles(metadataDir) {
|
|
732
|
+
logger.debug("🔧 Formatting generated JSON files with Biome...");
|
|
733
|
+
formatDirectoryWithProjectBiome(metadataDir);
|
|
623
734
|
}
|
|
624
735
|
async function generateMetadata(projectDirectory, metadataDirectory, options) {
|
|
625
736
|
try {
|
|
@@ -734,7 +845,7 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
|
|
|
734
845
|
else logger.info(`Generated ${allAspects.length} aspect metadata file(s)`);
|
|
735
846
|
}
|
|
736
847
|
const shouldLintFix = options?.lintFix !== false;
|
|
737
|
-
if (!dryRun && shouldLintFix && (allComponents.length > 0 || allPageTypes.length > 0 || allAspects.length > 0)) lintGeneratedFiles(metadataDir
|
|
848
|
+
if (!dryRun && shouldLintFix && (allComponents.length > 0 || allPageTypes.length > 0 || allAspects.length > 0)) lintGeneratedFiles(metadataDir);
|
|
738
849
|
return {
|
|
739
850
|
componentsGenerated: allComponents.length,
|
|
740
851
|
pageTypesGenerated: allPageTypes.length,
|