@salesforce/storefront-next-dev 0.3.1 → 0.4.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 +12 -12
- package/bin/run.js +19 -9
- package/dist/cartridge-services/index.js +9 -1
- package/dist/cartridge-services/index.js.map +1 -1
- package/dist/commands/config/inspect.js +191 -0
- package/dist/commands/create-bundle.js +1 -1
- package/dist/commands/create-storefront.js +3 -3
- package/dist/commands/dev.js +1 -3
- package/dist/commands/extensions/install.js +1 -1
- package/dist/commands/extensions/list.js +1 -1
- package/dist/commands/extensions/remove.js +1 -1
- package/dist/commands/generate-cartridge.js +1 -1
- package/dist/commands/locales/aggregate-extensions.js +181 -0
- package/dist/commands/prepare-local.js +1 -1
- package/dist/commands/preview.js +1 -3
- package/dist/commands/scapi/add.js +298 -0
- package/dist/commands/scapi/list.js +35 -0
- package/dist/commands/scapi/remove.js +56 -0
- package/dist/commands/validate-cartridge.js +1 -1
- package/dist/configs/react-router.config.js +2 -1
- package/dist/configs/react-router.config.js.map +1 -1
- package/dist/data-store/local-provider.d.ts +42 -0
- package/dist/data-store/local-provider.d.ts.map +1 -0
- package/dist/data-store/local-provider.js +66 -0
- package/dist/data-store/local-provider.js.map +1 -0
- package/dist/flags.js +5 -3
- package/dist/generate-cartridge.js +9 -1
- package/dist/generate-custom-clients.js +73 -0
- package/dist/hooks/init.js +29 -4
- package/dist/index.d.ts +46 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +260 -42
- 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/schema-utils.js +64 -0
- package/dist/utils.js +3 -11
- package/package.json +19 -3
|
@@ -0,0 +1,298 @@
|
|
|
1
|
+
import { i as writeSchemaMetadata, n as deriveClientKey, t as deriveBasePath } from "../../schema-utils.js";
|
|
2
|
+
import { t as generateCustomClientsFile } from "../../generate-custom-clients.js";
|
|
3
|
+
import { Args, Flags } from "@oclif/core";
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
5
|
+
import { basename, dirname, extname, join, relative, resolve } from "node:path";
|
|
6
|
+
import { OAuthCommand } from "@salesforce/b2c-tooling-sdk/cli";
|
|
7
|
+
import { createScapiSchemasClient, toOrganizationId } from "@salesforce/b2c-tooling-sdk/clients";
|
|
8
|
+
import YAML from "yaml";
|
|
9
|
+
|
|
10
|
+
//#region src/scapi/generate-operation-map.ts
|
|
11
|
+
/**
|
|
12
|
+
* Extract operation mappings from a generated TypeScript file.
|
|
13
|
+
*
|
|
14
|
+
* Parses the `paths` interface to find path strings, HTTP methods,
|
|
15
|
+
* and operation references (e.g., operations["getCategories"]).
|
|
16
|
+
*/
|
|
17
|
+
function extractOperationMap(filePath) {
|
|
18
|
+
const content = readFileSync(filePath, "utf-8");
|
|
19
|
+
const operations = {};
|
|
20
|
+
const lines = content.split("\n");
|
|
21
|
+
let currentPath = null;
|
|
22
|
+
const validMethods = [
|
|
23
|
+
"get",
|
|
24
|
+
"post",
|
|
25
|
+
"put",
|
|
26
|
+
"patch",
|
|
27
|
+
"delete",
|
|
28
|
+
"head",
|
|
29
|
+
"options"
|
|
30
|
+
];
|
|
31
|
+
let insidePathsInterface = false;
|
|
32
|
+
for (const line of lines) {
|
|
33
|
+
if (line.match(/^export interface paths \{/)) {
|
|
34
|
+
insidePathsInterface = true;
|
|
35
|
+
continue;
|
|
36
|
+
}
|
|
37
|
+
if (insidePathsInterface && line.match(/^\}$/)) break;
|
|
38
|
+
if (!insidePathsInterface) continue;
|
|
39
|
+
const pathMatch = line.match(/^\s+"([^"]+)":\s*\{/);
|
|
40
|
+
if (pathMatch) {
|
|
41
|
+
currentPath = pathMatch[1];
|
|
42
|
+
continue;
|
|
43
|
+
}
|
|
44
|
+
const operationMatch = line.match(/^\s+(\w+):\s*operations\["([^"]+)"\];?/);
|
|
45
|
+
if (operationMatch && currentPath) {
|
|
46
|
+
const [, method, operationName] = operationMatch;
|
|
47
|
+
if (validMethods.includes(method.toLowerCase())) operations[operationName] = {
|
|
48
|
+
method: method.toUpperCase(),
|
|
49
|
+
path: currentPath
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
if (currentPath && line.match(/^\s{4}\};?\s*$/)) currentPath = null;
|
|
53
|
+
}
|
|
54
|
+
return operations;
|
|
55
|
+
}
|
|
56
|
+
/**
|
|
57
|
+
* Find the longest common prefix for all paths.
|
|
58
|
+
*/
|
|
59
|
+
function findCommonBasePath(operations) {
|
|
60
|
+
const paths = Object.values(operations).map((op) => op.path);
|
|
61
|
+
if (paths.length === 0) return "";
|
|
62
|
+
if (paths.length === 1) {
|
|
63
|
+
const lastSlashIndex = paths[0].lastIndexOf("/");
|
|
64
|
+
return lastSlashIndex > 0 ? paths[0].substring(0, lastSlashIndex) : "";
|
|
65
|
+
}
|
|
66
|
+
let prefix = paths[0];
|
|
67
|
+
for (let i = 1; i < paths.length; i++) {
|
|
68
|
+
const current = paths[i];
|
|
69
|
+
let j = 0;
|
|
70
|
+
while (j < prefix.length && j < current.length && prefix[j] === current[j]) j++;
|
|
71
|
+
prefix = prefix.substring(0, j);
|
|
72
|
+
if (prefix.length === 0) return "";
|
|
73
|
+
}
|
|
74
|
+
const trimmed = prefix.replace(/\/+$/, "");
|
|
75
|
+
if (!trimmed || !trimmed.includes("/")) return "";
|
|
76
|
+
return trimmed;
|
|
77
|
+
}
|
|
78
|
+
/**
|
|
79
|
+
* Convert an operation map to optimized format with abbreviated keys.
|
|
80
|
+
*/
|
|
81
|
+
function convertToOptimizedOperations(operations, basePath) {
|
|
82
|
+
const optimizedOperations = {};
|
|
83
|
+
for (const [name, { method, path: opPath }] of Object.entries(operations)) optimizedOperations[name] = {
|
|
84
|
+
m: method,
|
|
85
|
+
b: basePath,
|
|
86
|
+
s: opPath.startsWith(basePath) ? opPath.substring(basePath.length) : opPath
|
|
87
|
+
};
|
|
88
|
+
return optimizedOperations;
|
|
89
|
+
}
|
|
90
|
+
/**
|
|
91
|
+
* Generate an operation map file from a generated TypeScript types file.
|
|
92
|
+
*
|
|
93
|
+
* @param inputFile - Path to the openapi-typescript generated .ts file
|
|
94
|
+
* @param outputFile - Path for the output .operations.ts file (defaults to same dir as input)
|
|
95
|
+
* @returns The output file path
|
|
96
|
+
*/
|
|
97
|
+
function generateOperationFile(inputFile, outputFile) {
|
|
98
|
+
const operations = extractOperationMap(inputFile);
|
|
99
|
+
const baseName = basename(inputFile, ".ts");
|
|
100
|
+
if (!outputFile) outputFile = join(dirname(inputFile), `${baseName}.operations.ts`);
|
|
101
|
+
if (Object.keys(operations).length === 0) throw new Error(`No operations found in ${baseName}.ts`);
|
|
102
|
+
const basePath = findCommonBasePath(operations);
|
|
103
|
+
const optimizedOps = convertToOptimizedOperations(operations, basePath);
|
|
104
|
+
const output = `/**
|
|
105
|
+
* Auto-generated operation map for ${baseName}
|
|
106
|
+
*
|
|
107
|
+
* Generated by sfnext scapi — do not edit manually.
|
|
108
|
+
*
|
|
109
|
+
* Property abbreviations:
|
|
110
|
+
* - m: HTTP method (GET, POST, PUT, PATCH, DELETE, etc.)
|
|
111
|
+
* - b: Base path shared across operations
|
|
112
|
+
* - s: Suffix path unique to this operation
|
|
113
|
+
*/
|
|
114
|
+
|
|
115
|
+
export const BASE_PATH = '${basePath}' as const;
|
|
116
|
+
|
|
117
|
+
export const operations = {
|
|
118
|
+
${Object.entries(optimizedOps).map(([name, { m, s }]) => ` ${name}: { m: '${m}' as const, b: BASE_PATH, s: '${s}' }`).join(",\n")}
|
|
119
|
+
} as const;
|
|
120
|
+
`;
|
|
121
|
+
writeFileSync(outputFile, output, "utf-8");
|
|
122
|
+
return outputFile;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/scapi/generate-types.ts
|
|
127
|
+
/**
|
|
128
|
+
* Generate TypeScript types and operation map from an OpenAPI schema file.
|
|
129
|
+
*
|
|
130
|
+
* @param schemaPath - Absolute path to the OAS YAML/JSON schema
|
|
131
|
+
* @param outputDir - Directory where generated files will be written
|
|
132
|
+
* @param baseName - Base name for the generated files (e.g., "shopper-products-v1")
|
|
133
|
+
* @returns Object with paths to the generated types and operations files
|
|
134
|
+
*/
|
|
135
|
+
async function generateFromSchema(schemaPath, outputDir, baseName) {
|
|
136
|
+
const openapiTS = await import("openapi-typescript");
|
|
137
|
+
const schemaUrl = new URL(`file://${schemaPath}`);
|
|
138
|
+
const ast = await openapiTS.default(schemaUrl);
|
|
139
|
+
const output = openapiTS.astToString(ast);
|
|
140
|
+
mkdirSync(outputDir, { recursive: true });
|
|
141
|
+
const typesFile = join(outputDir, `${baseName}.ts`);
|
|
142
|
+
writeFileSync(typesFile, output, "utf-8");
|
|
143
|
+
return {
|
|
144
|
+
typesFile,
|
|
145
|
+
operationsFile: generateOperationFile(typesFile)
|
|
146
|
+
};
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
//#endregion
|
|
150
|
+
//#region src/commands/scapi/add.ts
|
|
151
|
+
/**
|
|
152
|
+
* Add a custom SCAPI client.
|
|
153
|
+
*
|
|
154
|
+
* Supports two modes:
|
|
155
|
+
* 1. **Local schema**: Provide `--schema` to generate from a local OpenAPI schema file.
|
|
156
|
+
* 2. **Pull from API**: Provide positional args `<apiFamily> <apiName> <apiVersion>` to
|
|
157
|
+
* fetch the schema from the SCAPI Schemas API (requires OAuth credentials).
|
|
158
|
+
*
|
|
159
|
+
* @example
|
|
160
|
+
* ```bash
|
|
161
|
+
* # From a local schema file
|
|
162
|
+
* sfnext scapi add --schema ./my-schemas/loyalty-api.yaml --name loyalty --base-path /custom/loyalty/v1
|
|
163
|
+
*
|
|
164
|
+
* # Pull from the SCAPI Schemas API
|
|
165
|
+
* sfnext scapi add custom loyalty v1
|
|
166
|
+
* ```
|
|
167
|
+
*
|
|
168
|
+
* @env SFCC_SHORTCODE - SCAPI short code (for API pull mode)
|
|
169
|
+
* @env SFCC_TENANT_ID - Tenant ID (for API pull mode)
|
|
170
|
+
* @env SFCC_OAUTH_CLIENT_ID - OAuth client ID (for API pull mode)
|
|
171
|
+
* @env SFCC_OAUTH_CLIENT_SECRET - OAuth client secret (for API pull mode)
|
|
172
|
+
*/
|
|
173
|
+
var Add = class Add extends OAuthCommand {
|
|
174
|
+
static description = "Add a custom SCAPI client from a local schema or the SCAPI Schemas API";
|
|
175
|
+
static examples = [
|
|
176
|
+
"<%= config.bin %> <%= command.id %> --schema ./custom-api.yaml --name myCustomApi --base-path /custom/my-api/v1",
|
|
177
|
+
"<%= config.bin %> <%= command.id %> custom loyalty v1",
|
|
178
|
+
"<%= config.bin %> <%= command.id %> custom loyalty v1 --expand-custom-properties"
|
|
179
|
+
];
|
|
180
|
+
static args = {
|
|
181
|
+
apiFamily: Args.string({
|
|
182
|
+
description: "API family (e.g., custom) — for pulling from the SCAPI Schemas API",
|
|
183
|
+
required: false
|
|
184
|
+
}),
|
|
185
|
+
apiName: Args.string({
|
|
186
|
+
description: "API name (e.g., loyalty, store-inventory)",
|
|
187
|
+
required: false
|
|
188
|
+
}),
|
|
189
|
+
apiVersion: Args.string({
|
|
190
|
+
description: "API version (e.g., v1)",
|
|
191
|
+
required: false
|
|
192
|
+
})
|
|
193
|
+
};
|
|
194
|
+
static flags = {
|
|
195
|
+
...OAuthCommand.baseFlags,
|
|
196
|
+
schema: Flags.string({
|
|
197
|
+
description: "Path to a local OpenAPI schema file (YAML or JSON)",
|
|
198
|
+
exclusive: ["expand-custom-properties"]
|
|
199
|
+
}),
|
|
200
|
+
name: Flags.string({ description: "Client key name (e.g., \"loyalty\", \"storeInventory\"). Defaults to camelCase of apiName when pulling." }),
|
|
201
|
+
"base-path": Flags.string({ description: "SCAPI base path prefix (auto-derived from schema if not provided)" }),
|
|
202
|
+
"supports-locale": Flags.boolean({
|
|
203
|
+
description: "Whether this API supports the locale query parameter",
|
|
204
|
+
allowNo: true
|
|
205
|
+
}),
|
|
206
|
+
"expand-custom-properties": Flags.boolean({
|
|
207
|
+
description: "Include custom properties (c_*) in the schema (API pull only)",
|
|
208
|
+
default: true,
|
|
209
|
+
allowNo: true
|
|
210
|
+
})
|
|
211
|
+
};
|
|
212
|
+
async run() {
|
|
213
|
+
const { args, flags } = await this.parse(Add);
|
|
214
|
+
const projectDir = flags["project-directory"] ?? process.cwd();
|
|
215
|
+
const isPull = !flags.schema;
|
|
216
|
+
if (isPull) {
|
|
217
|
+
if (!args.apiFamily || !args.apiName || !args.apiVersion) this.error("Provide either --schema for a local file, or <apiFamily> <apiName> <apiVersion> to pull from the API.");
|
|
218
|
+
}
|
|
219
|
+
let schemaPath;
|
|
220
|
+
let schemaName;
|
|
221
|
+
let clientKey;
|
|
222
|
+
if (isPull) {
|
|
223
|
+
const { apiFamily, apiName, apiVersion } = args;
|
|
224
|
+
clientKey = flags.name ?? deriveClientKey(apiName);
|
|
225
|
+
const { shortCode, tenantId } = this.resolvedConfig.values;
|
|
226
|
+
if (!shortCode) this.error("SCAPI short code required. Provide --short-code, set SFCC_SHORTCODE, or configure short-code in dw.json.");
|
|
227
|
+
if (!tenantId) this.error("tenant-id is required. Provide via --tenant-id flag, SFCC_TENANT_ID env var, or tenant-id in dw.json.");
|
|
228
|
+
const organizationId = toOrganizationId(tenantId);
|
|
229
|
+
const oauthStrategy = this.getOAuthStrategy();
|
|
230
|
+
const client = createScapiSchemasClient({
|
|
231
|
+
shortCode,
|
|
232
|
+
tenantId
|
|
233
|
+
}, oauthStrategy);
|
|
234
|
+
this.log(`Fetching schema: ${apiFamily}/${apiName}/${apiVersion}...`);
|
|
235
|
+
const { data, error, response } = await client.GET("/organizations/{organizationId}/schemas/{apiFamily}/{apiName}/{apiVersion}", { params: {
|
|
236
|
+
path: {
|
|
237
|
+
organizationId,
|
|
238
|
+
apiFamily,
|
|
239
|
+
apiName,
|
|
240
|
+
apiVersion
|
|
241
|
+
},
|
|
242
|
+
query: flags["expand-custom-properties"] ? { expand: "custom_properties" } : {}
|
|
243
|
+
} });
|
|
244
|
+
if (error || !data) this.error(`Failed to fetch schema: ${response.status} ${response.statusText}`);
|
|
245
|
+
const schemasDir = join(join(projectDir, "src", "scapi"), "schemas");
|
|
246
|
+
mkdirSync(schemasDir, { recursive: true });
|
|
247
|
+
schemaName = `${apiName}-${apiVersion}`;
|
|
248
|
+
schemaPath = join(schemasDir, `${schemaName}.yaml`);
|
|
249
|
+
writeFileSync(schemaPath, YAML.stringify(data), "utf-8");
|
|
250
|
+
this.log(`Saved schema to ${relative(projectDir, schemaPath)}`);
|
|
251
|
+
} else {
|
|
252
|
+
const schemaFlag = flags.schema;
|
|
253
|
+
if (!schemaFlag) throw new TypeError("Expected --schema when running in local schema mode.");
|
|
254
|
+
const schemaInput = resolve(projectDir, schemaFlag);
|
|
255
|
+
if (!existsSync(schemaInput)) this.error(`Schema file not found: ${schemaInput}`);
|
|
256
|
+
if (!flags.name) this.error("--name is required when using --schema (e.g., --name loyalty)");
|
|
257
|
+
clientKey = flags.name;
|
|
258
|
+
const ext = extname(schemaInput);
|
|
259
|
+
schemaName = basename(schemaInput, ext);
|
|
260
|
+
const schemasDir = join(join(projectDir, "src", "scapi"), "schemas");
|
|
261
|
+
mkdirSync(schemasDir, { recursive: true });
|
|
262
|
+
schemaPath = join(schemasDir, `${schemaName}${ext}`);
|
|
263
|
+
copyFileSync(schemaInput, schemaPath);
|
|
264
|
+
this.log(`Copied schema to ${relative(projectDir, schemaPath)}`);
|
|
265
|
+
}
|
|
266
|
+
const scapiDir = join(projectDir, "src", "scapi");
|
|
267
|
+
const generatedDir = join(scapiDir, "generated");
|
|
268
|
+
mkdirSync(generatedDir, { recursive: true });
|
|
269
|
+
let basePath = flags["base-path"];
|
|
270
|
+
if (!basePath) {
|
|
271
|
+
basePath = deriveBasePath(schemaPath);
|
|
272
|
+
if (!basePath) if (isPull) {
|
|
273
|
+
const { apiFamily, apiName, apiVersion } = args;
|
|
274
|
+
basePath = `/${apiFamily}/${apiName}/${apiVersion}`;
|
|
275
|
+
this.log(`Constructed base path from API coordinates: ${basePath}`);
|
|
276
|
+
} else this.error("Could not derive base path from schema. Please provide --base-path explicitly.");
|
|
277
|
+
else this.log(`Derived base path from schema: ${basePath}`);
|
|
278
|
+
}
|
|
279
|
+
const supportsLocale = flags["supports-locale"] ?? false;
|
|
280
|
+
this.log(`Registering custom client: ${clientKey}`);
|
|
281
|
+
writeSchemaMetadata(join(scapiDir, "schemas"), schemaName, {
|
|
282
|
+
clientKey,
|
|
283
|
+
basePath,
|
|
284
|
+
supportsLocale,
|
|
285
|
+
orgPrefix: true
|
|
286
|
+
});
|
|
287
|
+
this.log("Generating TypeScript types...");
|
|
288
|
+
const { typesFile, operationsFile } = await generateFromSchema(schemaPath, generatedDir, schemaName);
|
|
289
|
+
this.log(`Generated types: ${relative(projectDir, typesFile)}`);
|
|
290
|
+
this.log(`Generated operations: ${relative(projectDir, operationsFile)}`);
|
|
291
|
+
generateCustomClientsFile(scapiDir);
|
|
292
|
+
this.log(`Updated ${relative(projectDir, join(scapiDir, "custom-clients.ts"))}`);
|
|
293
|
+
this.log(`\nDone! Custom client "${clientKey}" is ready.`);
|
|
294
|
+
}
|
|
295
|
+
};
|
|
296
|
+
|
|
297
|
+
//#endregion
|
|
298
|
+
export { Add as default };
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { r as commonFlags } from "../../flags.js";
|
|
2
|
+
import { r as readAllSchemaMetadata } from "../../schema-utils.js";
|
|
3
|
+
import { Command } from "@oclif/core";
|
|
4
|
+
import { join } from "node:path";
|
|
5
|
+
|
|
6
|
+
//#region src/commands/scapi/list.ts
|
|
7
|
+
/**
|
|
8
|
+
* List registered custom SCAPI clients.
|
|
9
|
+
*/
|
|
10
|
+
var List = class List extends Command {
|
|
11
|
+
static description = "List registered custom SCAPI clients";
|
|
12
|
+
static examples = ["<%= config.bin %> <%= command.id %>", "<%= config.bin %> <%= command.id %> -d ./my-project"];
|
|
13
|
+
static flags = { ...commonFlags };
|
|
14
|
+
async run() {
|
|
15
|
+
const { flags } = await this.parse(List);
|
|
16
|
+
const projectDir = flags["project-directory"];
|
|
17
|
+
const entries = readAllSchemaMetadata(join(projectDir, "src", "scapi", "schemas"));
|
|
18
|
+
if (entries.length === 0) {
|
|
19
|
+
this.log("No custom SCAPI clients registered.");
|
|
20
|
+
this.log("Use `sfnext scapi add` to add one.");
|
|
21
|
+
return;
|
|
22
|
+
}
|
|
23
|
+
this.log(`\nRegistered SCAPI clients (${entries.length}):\n`);
|
|
24
|
+
for (const { clientKey, basePath, supportsLocale, schemaName } of entries) {
|
|
25
|
+
this.log(` ${clientKey}`);
|
|
26
|
+
this.log(` Schema: schemas/${schemaName}.yaml`);
|
|
27
|
+
this.log(` Base: ${basePath}`);
|
|
28
|
+
this.log(` Locale: ${supportsLocale ? "yes" : "no"}`);
|
|
29
|
+
this.log("");
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
};
|
|
33
|
+
|
|
34
|
+
//#endregion
|
|
35
|
+
export { List as default };
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { r as commonFlags } from "../../flags.js";
|
|
2
|
+
import { r as readAllSchemaMetadata } from "../../schema-utils.js";
|
|
3
|
+
import { t as generateCustomClientsFile } from "../../generate-custom-clients.js";
|
|
4
|
+
import { Args, Command } from "@oclif/core";
|
|
5
|
+
import { existsSync, unlinkSync } from "node:fs";
|
|
6
|
+
import { join, relative } from "node:path";
|
|
7
|
+
|
|
8
|
+
//#region src/commands/scapi/remove.ts
|
|
9
|
+
/**
|
|
10
|
+
* Remove a registered custom SCAPI client.
|
|
11
|
+
*/
|
|
12
|
+
var Remove = class Remove extends Command {
|
|
13
|
+
static description = "Remove a registered custom SCAPI client";
|
|
14
|
+
static examples = ["<%= config.bin %> <%= command.id %> loyalty", "<%= config.bin %> <%= command.id %> myCustomApi -d ./my-project"];
|
|
15
|
+
static args = { name: Args.string({
|
|
16
|
+
description: "Client key name to remove (e.g., \"loyalty\" or \"storeInventory\")",
|
|
17
|
+
required: true
|
|
18
|
+
}) };
|
|
19
|
+
static flags = { ...commonFlags };
|
|
20
|
+
async run() {
|
|
21
|
+
const { args, flags } = await this.parse(Remove);
|
|
22
|
+
const projectDir = flags["project-directory"];
|
|
23
|
+
const clientKey = args.name;
|
|
24
|
+
const scapiDir = join(projectDir, "src", "scapi");
|
|
25
|
+
const schemasDir = join(scapiDir, "schemas");
|
|
26
|
+
const generatedDir = join(scapiDir, "generated");
|
|
27
|
+
const entry = readAllSchemaMetadata(schemasDir).find((e) => e.clientKey === clientKey);
|
|
28
|
+
if (!entry) this.error(`No registered client found with key "${clientKey}". Run \`sfnext scapi list\` to see registered clients.`);
|
|
29
|
+
const { schemaName } = entry;
|
|
30
|
+
for (const ext of [
|
|
31
|
+
".yaml",
|
|
32
|
+
".yml",
|
|
33
|
+
".json"
|
|
34
|
+
]) {
|
|
35
|
+
const schemaPath = join(schemasDir, `${schemaName}${ext}`);
|
|
36
|
+
if (existsSync(schemaPath)) {
|
|
37
|
+
unlinkSync(schemaPath);
|
|
38
|
+
this.log(`Removed ${relative(projectDir, schemaPath)}`);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
const metaPath = join(schemasDir, `${schemaName}.meta.json`);
|
|
42
|
+
if (existsSync(metaPath)) unlinkSync(metaPath);
|
|
43
|
+
const typesPath = join(generatedDir, `${schemaName}.ts`);
|
|
44
|
+
const opsPath = join(generatedDir, `${schemaName}.operations.ts`);
|
|
45
|
+
for (const filePath of [typesPath, opsPath]) if (existsSync(filePath)) {
|
|
46
|
+
unlinkSync(filePath);
|
|
47
|
+
this.log(`Removed ${relative(projectDir, filePath)}`);
|
|
48
|
+
}
|
|
49
|
+
generateCustomClientsFile(scapiDir);
|
|
50
|
+
this.log(`Updated ${relative(projectDir, join(scapiDir, "custom-clients.ts"))}`);
|
|
51
|
+
this.log(`\nRemoved client "${clientKey}".`);
|
|
52
|
+
}
|
|
53
|
+
};
|
|
54
|
+
|
|
55
|
+
//#endregion
|
|
56
|
+
export { Remove as default };
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
|
|
2
|
-
import {
|
|
2
|
+
import { r as commonFlags } from "../flags.js";
|
|
3
3
|
import { t as validateCartridgeMetadata } from "../validate-cartridge.js";
|
|
4
4
|
import { Command } from "@oclif/core";
|
|
5
5
|
import path from "path";
|
|
@@ -48,7 +48,8 @@ function storefrontNextPreset() {
|
|
|
48
48
|
ssr: true,
|
|
49
49
|
future: {
|
|
50
50
|
v8_middleware: true,
|
|
51
|
-
v8_viteEnvironmentApi: true
|
|
51
|
+
v8_viteEnvironmentApi: true,
|
|
52
|
+
unstable_optimizeDeps: true
|
|
52
53
|
},
|
|
53
54
|
basename: getBasePath() || "/",
|
|
54
55
|
...sfwFalconInstance && { allowedActionOrigins: [`*.dataplane.cvw-dataplane-test.${sfwFalconInstance}.aws.sfdc.cl`] }
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"react-router.config.js","names":["errors: string[]"],"sources":["../../src/utils/paths.ts","../../src/configs/react-router.config.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Normalize a file path to use forward slashes.\n * On Windows, Node APIs return backslash-separated paths, but ESM import\n * specifiers and Vite module IDs require forward slashes.\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/');\n}\n\n/**\n * Get the Commerce Cloud API URL from a short code\n */\nexport function getCommerceCloudApiUrl(shortCode: string, proxyHost?: string): string {\n return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;\n}\n\n/**\n * Get the configurable base path for the application.\n * Reads from MRT_ENV_BASE_PATH environment variable.\n *\n * The base path is used for CDN routing to the correct MRT environment.\n * It is prepended to all URLs: page routes, /mobify/bundle/ assets, and /mobify/proxy/api.\n *\n * Validation rules:\n * - Must be a single path segment starting with '/'\n * - Max 63 characters after the leading slash\n * - Only URL-safe characters allowed\n * - Returns empty string if not set\n *\n * @returns The sanitized base path (e.g., '/site-a' or '')\n *\n * @example\n * // No base path configured\n * getBasePath() // → ''\n *\n * // With base path '/storefront'\n * getBasePath() // → '/storefront'\n *\n * // Automatically sanitizes\n * // MRT_ENV_BASE_PATH='storefront/' → '/storefront'\n */\nexport function getBasePath(): string {\n const basePath = process.env.MRT_ENV_BASE_PATH?.trim();\n\n // Return empty string if not set or empty\n if (!basePath) {\n return '';\n }\n\n // Base path prefix must be a single path segment starting with '/', max 63 chars,\n // using only URL-safe characters (alphanumeric, hyphens, underscores, dots, and other safe symbols)\n // This aligns with the regex used by MRT\n if (!/^\\/[a-zA-Z0-9_.+$~\"'@:-]{1,63}$/.test(basePath)) {\n throw new Error(\n `Invalid base path: \"${basePath}\". ` +\n \"Base path must be a single segment starting with '/' (e.g., '/site-a'), \" +\n 'contain only URL-safe characters, and be at most 63 characters after the leading slash.'\n );\n }\n\n return basePath;\n}\n\n/**\n * Get the bundle path for static assets\n */\nexport function getBundlePath(bundleId: string): string {\n const basePath = getBasePath();\n return `${basePath}/mobify/bundle/${bundleId}/client/`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Preset } from '@react-router/dev/config';\nimport { getBasePath } from '../utils/paths';\n\n/**\n * Storefront Next preset for React Router configuration.\n * This preset enforces standard configuration for SFCC Storefront Next applications.\n * Users cannot override these values - they will be validated and an error will be thrown if modified.\n */\nexport function storefrontNextPreset(): Preset {\n const sfwFalconInstance = process.env.SFW_FALCON_INSTANCE;\n\n // Read base path from env var for basename configuration\n // This sets the base path for all React Router routes (e.g., '/site-a')\n // In dev: reads from .env at build time\n // In production: baked into the build, but can be overridden at runtime via patchReactRouterBuild\n const basePath = getBasePath();\n\n const presetConfig = {\n appDirectory: './src',\n buildDirectory: 'build',\n routeDiscovery: { mode: 'initial' as const },\n serverModuleFormat: 'cjs' as const,\n ssr: true,\n future: {\n v8_middleware: true,\n v8_viteEnvironmentApi: true,\n },\n // Set basename from base path for CDN routing\n // When set, all routes are served under this base path (e.g., /site-a/category/womens)\n // React Router automatically handles Link, navigate, .data requests, and redirects\n basename: basePath || '/',\n // Allow workspace proxy domain for CSRF protection on form actions\n ...(sfwFalconInstance && {\n allowedActionOrigins: [`*.dataplane.cvw-dataplane-test.${sfwFalconInstance}.aws.sfdc.cl`],\n }),\n };\n\n return {\n name: 'storefront-next-preset',\n reactRouterConfig: () => presetConfig,\n reactRouterConfigResolved: ({ reactRouterConfig }) => {\n // Validate that critical config values have not been overridden\n // Note: We don't validate appDirectory and buildDirectory because they get resolved\n // to absolute paths and we can't reliably determine the correct absolute path\n const errors: string[] = [];\n\n if (reactRouterConfig.routeDiscovery?.mode !== presetConfig.routeDiscovery.mode) {\n errors.push(\n `routeDiscovery.mode: expected \"${presetConfig.routeDiscovery.mode}\", got \"${reactRouterConfig.routeDiscovery?.mode}\"`\n );\n }\n\n if (reactRouterConfig.serverModuleFormat !== presetConfig.serverModuleFormat) {\n errors.push(\n `serverModuleFormat: expected \"${presetConfig.serverModuleFormat}\", got \"${reactRouterConfig.serverModuleFormat}\"`\n );\n }\n\n if (reactRouterConfig.ssr !== presetConfig.ssr) {\n errors.push(`ssr: expected ${presetConfig.ssr}, got ${reactRouterConfig.ssr}`);\n }\n\n if (reactRouterConfig.future?.v8_middleware !== presetConfig.future.v8_middleware) {\n errors.push(\n `future.v8_middleware: expected ${presetConfig.future.v8_middleware}, got ${reactRouterConfig.future?.v8_middleware}`\n );\n }\n\n if (reactRouterConfig.future?.v8_viteEnvironmentApi !== presetConfig.future.v8_viteEnvironmentApi) {\n errors.push(\n `future.v8_viteEnvironmentApi: expected ${presetConfig.future.v8_viteEnvironmentApi}, got ${reactRouterConfig.future?.v8_viteEnvironmentApi}`\n );\n }\n\n if (reactRouterConfig.basename !== presetConfig.basename) {\n errors.push(`basename: expected ${presetConfig.basename}, got ${reactRouterConfig.basename}`);\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Storefront Next preset configuration was overridden. The following values must not be modified:\\n${errors.map((e) => ` - ${e}`).join('\\n')}`\n );\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,cAAsB;CAClC,MAAM,WAAW,QAAQ,IAAI,mBAAmB,MAAM;AAGtD,KAAI,CAAC,SACD,QAAO;AAMX,KAAI,CAAC,kCAAkC,KAAK,SAAS,CACjD,OAAM,IAAI,MACN,uBAAuB,SAAS,oKAGnC;AAGL,QAAO;;;;;;;;;;ACpDX,SAAgB,uBAA+B;CAC3C,MAAM,oBAAoB,QAAQ,IAAI;CAQtC,MAAM,eAAe;EACjB,cAAc;EACd,gBAAgB;EAChB,gBAAgB,EAAE,MAAM,WAAoB;EAC5C,oBAAoB;EACpB,KAAK;EACL,QAAQ;GACJ,eAAe;GACf,uBAAuB;GAC1B;EAID,
|
|
1
|
+
{"version":3,"file":"react-router.config.js","names":["errors: string[]"],"sources":["../../src/utils/paths.ts","../../src/configs/react-router.config.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n/**\n * Normalize a file path to use forward slashes.\n * On Windows, Node APIs return backslash-separated paths, but ESM import\n * specifiers and Vite module IDs require forward slashes.\n */\nexport function toPosixPath(filePath: string): string {\n return filePath.replace(/\\\\/g, '/');\n}\n\n/**\n * Get the Commerce Cloud API URL from a short code\n */\nexport function getCommerceCloudApiUrl(shortCode: string, proxyHost?: string): string {\n return proxyHost || `https://${shortCode}.api.commercecloud.salesforce.com`;\n}\n\n/**\n * Get the configurable base path for the application.\n * Reads from MRT_ENV_BASE_PATH environment variable.\n *\n * The base path is used for CDN routing to the correct MRT environment.\n * It is prepended to all URLs: page routes, /mobify/bundle/ assets, and /mobify/proxy/api.\n *\n * Validation rules:\n * - Must be a single path segment starting with '/'\n * - Max 63 characters after the leading slash\n * - Only URL-safe characters allowed\n * - Returns empty string if not set\n *\n * @returns The sanitized base path (e.g., '/site-a' or '')\n *\n * @example\n * // No base path configured\n * getBasePath() // → ''\n *\n * // With base path '/storefront'\n * getBasePath() // → '/storefront'\n *\n * // Automatically sanitizes\n * // MRT_ENV_BASE_PATH='storefront/' → '/storefront'\n */\nexport function getBasePath(): string {\n const basePath = process.env.MRT_ENV_BASE_PATH?.trim();\n\n // Return empty string if not set or empty\n if (!basePath) {\n return '';\n }\n\n // Base path prefix must be a single path segment starting with '/', max 63 chars,\n // using only URL-safe characters (alphanumeric, hyphens, underscores, dots, and other safe symbols)\n // This aligns with the regex used by MRT\n if (!/^\\/[a-zA-Z0-9_.+$~\"'@:-]{1,63}$/.test(basePath)) {\n throw new Error(\n `Invalid base path: \"${basePath}\". ` +\n \"Base path must be a single segment starting with '/' (e.g., '/site-a'), \" +\n 'contain only URL-safe characters, and be at most 63 characters after the leading slash.'\n );\n }\n\n return basePath;\n}\n\n/**\n * Get the bundle path for static assets\n */\nexport function getBundlePath(bundleId: string): string {\n const basePath = getBasePath();\n return `${basePath}/mobify/bundle/${bundleId}/client/`;\n}\n","/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\nimport type { Preset } from '@react-router/dev/config';\nimport { getBasePath } from '../utils/paths';\n\n/**\n * Storefront Next preset for React Router configuration.\n * This preset enforces standard configuration for SFCC Storefront Next applications.\n * Users cannot override these values - they will be validated and an error will be thrown if modified.\n */\nexport function storefrontNextPreset(): Preset {\n const sfwFalconInstance = process.env.SFW_FALCON_INSTANCE;\n\n // Read base path from env var for basename configuration\n // This sets the base path for all React Router routes (e.g., '/site-a')\n // In dev: reads from .env at build time\n // In production: baked into the build, but can be overridden at runtime via patchReactRouterBuild\n const basePath = getBasePath();\n\n const presetConfig = {\n appDirectory: './src',\n buildDirectory: 'build',\n routeDiscovery: { mode: 'initial' as const },\n serverModuleFormat: 'cjs' as const,\n ssr: true,\n future: {\n v8_middleware: true,\n v8_viteEnvironmentApi: true,\n unstable_optimizeDeps: true,\n },\n // Set basename from base path for CDN routing\n // When set, all routes are served under this base path (e.g., /site-a/category/womens)\n // React Router automatically handles Link, navigate, .data requests, and redirects\n basename: basePath || '/',\n // Allow workspace proxy domain for CSRF protection on form actions\n ...(sfwFalconInstance && {\n allowedActionOrigins: [`*.dataplane.cvw-dataplane-test.${sfwFalconInstance}.aws.sfdc.cl`],\n }),\n };\n\n return {\n name: 'storefront-next-preset',\n reactRouterConfig: () => presetConfig,\n reactRouterConfigResolved: ({ reactRouterConfig }) => {\n // Validate that critical config values have not been overridden\n // Note: We don't validate appDirectory and buildDirectory because they get resolved\n // to absolute paths and we can't reliably determine the correct absolute path\n const errors: string[] = [];\n\n if (reactRouterConfig.routeDiscovery?.mode !== presetConfig.routeDiscovery.mode) {\n errors.push(\n `routeDiscovery.mode: expected \"${presetConfig.routeDiscovery.mode}\", got \"${reactRouterConfig.routeDiscovery?.mode}\"`\n );\n }\n\n if (reactRouterConfig.serverModuleFormat !== presetConfig.serverModuleFormat) {\n errors.push(\n `serverModuleFormat: expected \"${presetConfig.serverModuleFormat}\", got \"${reactRouterConfig.serverModuleFormat}\"`\n );\n }\n\n if (reactRouterConfig.ssr !== presetConfig.ssr) {\n errors.push(`ssr: expected ${presetConfig.ssr}, got ${reactRouterConfig.ssr}`);\n }\n\n if (reactRouterConfig.future?.v8_middleware !== presetConfig.future.v8_middleware) {\n errors.push(\n `future.v8_middleware: expected ${presetConfig.future.v8_middleware}, got ${reactRouterConfig.future?.v8_middleware}`\n );\n }\n\n if (reactRouterConfig.future?.v8_viteEnvironmentApi !== presetConfig.future.v8_viteEnvironmentApi) {\n errors.push(\n `future.v8_viteEnvironmentApi: expected ${presetConfig.future.v8_viteEnvironmentApi}, got ${reactRouterConfig.future?.v8_viteEnvironmentApi}`\n );\n }\n\n if (reactRouterConfig.basename !== presetConfig.basename) {\n errors.push(`basename: expected ${presetConfig.basename}, got ${reactRouterConfig.basename}`);\n }\n\n if (errors.length > 0) {\n throw new Error(\n `Storefront Next preset configuration was overridden. The following values must not be modified:\\n${errors.map((e) => ` - ${e}`).join('\\n')}`\n );\n }\n },\n };\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;AAwDA,SAAgB,cAAsB;CAClC,MAAM,WAAW,QAAQ,IAAI,mBAAmB,MAAM;AAGtD,KAAI,CAAC,SACD,QAAO;AAMX,KAAI,CAAC,kCAAkC,KAAK,SAAS,CACjD,OAAM,IAAI,MACN,uBAAuB,SAAS,oKAGnC;AAGL,QAAO;;;;;;;;;;ACpDX,SAAgB,uBAA+B;CAC3C,MAAM,oBAAoB,QAAQ,IAAI;CAQtC,MAAM,eAAe;EACjB,cAAc;EACd,gBAAgB;EAChB,gBAAgB,EAAE,MAAM,WAAoB;EAC5C,oBAAoB;EACpB,KAAK;EACL,QAAQ;GACJ,eAAe;GACf,uBAAuB;GACvB,uBAAuB;GAC1B;EAID,UAhBa,aAAa,IAgBJ;EAEtB,GAAI,qBAAqB,EACrB,sBAAsB,CAAC,kCAAkC,kBAAkB,cAAc,EAC5F;EACJ;AAED,QAAO;EACH,MAAM;EACN,yBAAyB;EACzB,4BAA4B,EAAE,wBAAwB;GAIlD,MAAMA,SAAmB,EAAE;AAE3B,OAAI,kBAAkB,gBAAgB,SAAS,aAAa,eAAe,KACvE,QAAO,KACH,kCAAkC,aAAa,eAAe,KAAK,UAAU,kBAAkB,gBAAgB,KAAK,GACvH;AAGL,OAAI,kBAAkB,uBAAuB,aAAa,mBACtD,QAAO,KACH,iCAAiC,aAAa,mBAAmB,UAAU,kBAAkB,mBAAmB,GACnH;AAGL,OAAI,kBAAkB,QAAQ,aAAa,IACvC,QAAO,KAAK,iBAAiB,aAAa,IAAI,QAAQ,kBAAkB,MAAM;AAGlF,OAAI,kBAAkB,QAAQ,kBAAkB,aAAa,OAAO,cAChE,QAAO,KACH,kCAAkC,aAAa,OAAO,cAAc,QAAQ,kBAAkB,QAAQ,gBACzG;AAGL,OAAI,kBAAkB,QAAQ,0BAA0B,aAAa,OAAO,sBACxE,QAAO,KACH,0CAA0C,aAAa,OAAO,sBAAsB,QAAQ,kBAAkB,QAAQ,wBACzH;AAGL,OAAI,kBAAkB,aAAa,aAAa,SAC5C,QAAO,KAAK,sBAAsB,aAAa,SAAS,QAAQ,kBAAkB,WAAW;AAGjG,OAAI,OAAO,SAAS,EAChB,OAAM,IAAI,MACN,oGAAoG,OAAO,KAAK,MAAM,OAAO,IAAI,CAAC,KAAK,KAAK,GAC/I;;EAGZ"}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
//#region src/data-store/local-provider.d.ts
|
|
2
|
+
/**
|
|
3
|
+
* Copyright 2026 Salesforce, Inc.
|
|
4
|
+
*
|
|
5
|
+
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
6
|
+
* you may not use this file except in compliance with the License.
|
|
7
|
+
* You may obtain a copy of the License at
|
|
8
|
+
*
|
|
9
|
+
* http://www.apache.org/licenses/LICENSE-2.0
|
|
10
|
+
*
|
|
11
|
+
* Unless required by applicable law or agreed to in writing, software
|
|
12
|
+
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
13
|
+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
14
|
+
* See the License for the specific language governing permissions and
|
|
15
|
+
* limitations under the License.
|
|
16
|
+
*/
|
|
17
|
+
type DataStoreEntry = {
|
|
18
|
+
value?: unknown;
|
|
19
|
+
};
|
|
20
|
+
type LocalDataStoreProvider = {
|
|
21
|
+
kind: 'local';
|
|
22
|
+
getEntry: (key: string) => Promise<DataStoreEntry | null>;
|
|
23
|
+
};
|
|
24
|
+
type LocalDataStoreProviderOptions = {
|
|
25
|
+
defaults?: Record<string, Record<string, unknown>>;
|
|
26
|
+
warnOnMissing?: boolean;
|
|
27
|
+
};
|
|
28
|
+
/**
|
|
29
|
+
* Create a local data-store provider for development environments.
|
|
30
|
+
*
|
|
31
|
+
* Environment variables:
|
|
32
|
+
* - `SFNEXT_DATA_STORE_DEFAULTS` (optional): JSON map of keys to preference objects.
|
|
33
|
+
* Example: {"custom-global-preferences":{"featureFlag":true}}
|
|
34
|
+
* - `SFNEXT_DATA_STORE_WARN_ON_MISSING` (optional): Set to "false" to silence warnings.
|
|
35
|
+
*
|
|
36
|
+
* @param options - Optional defaults and warning controls for local entries.
|
|
37
|
+
* @returns Local provider that supplies preferences from defaults or empty values.
|
|
38
|
+
*/
|
|
39
|
+
declare function createLocalDataStoreProvider(options?: LocalDataStoreProviderOptions): LocalDataStoreProvider;
|
|
40
|
+
//#endregion
|
|
41
|
+
export { LocalDataStoreProvider, createLocalDataStoreProvider };
|
|
42
|
+
//# sourceMappingURL=local-provider.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-provider.d.ts","names":[],"sources":["../../src/data-store/local-provider.ts"],"sourcesContent":[],"mappings":";;;AAoBA;AAGE;AAkBF;;;;;;;;;;;KAzBK,cAAA;;;KAIO,sBAAA;;6BAEmB,QAAQ;;KAGlC,6BAAA;aACU,eAAe;;;;;;;;;;;;;;iBAed,4BAAA,WAAsC,gCAAqC"}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
//#region src/data-store/local-provider.ts
|
|
2
|
+
/**
|
|
3
|
+
* Create a local data-store provider for development environments.
|
|
4
|
+
*
|
|
5
|
+
* Environment variables:
|
|
6
|
+
* - `SFNEXT_DATA_STORE_DEFAULTS` (optional): JSON map of keys to preference objects.
|
|
7
|
+
* Example: {"custom-global-preferences":{"featureFlag":true}}
|
|
8
|
+
* - `SFNEXT_DATA_STORE_WARN_ON_MISSING` (optional): Set to "false" to silence warnings.
|
|
9
|
+
*
|
|
10
|
+
* @param options - Optional defaults and warning controls for local entries.
|
|
11
|
+
* @returns Local provider that supplies preferences from defaults or empty values.
|
|
12
|
+
*/
|
|
13
|
+
function createLocalDataStoreProvider(options = {}) {
|
|
14
|
+
const defaults = options.defaults ?? readDefaultsFromEnv();
|
|
15
|
+
const warnOnMissing = options.warnOnMissing ?? readWarnOnMissingFromEnv();
|
|
16
|
+
const warnedKeys = /* @__PURE__ */ new Set();
|
|
17
|
+
return {
|
|
18
|
+
kind: "local",
|
|
19
|
+
getEntry(key) {
|
|
20
|
+
const value = defaults[key];
|
|
21
|
+
if (value && typeof value === "object") return Promise.resolve({ value });
|
|
22
|
+
if (warnOnMissing && !warnedKeys.has(key)) {
|
|
23
|
+
warnedKeys.add(key);
|
|
24
|
+
console.warn(`Local data-store provider did not find '${key}'. Returning an empty object for development.`);
|
|
25
|
+
}
|
|
26
|
+
return Promise.resolve({ value: {} });
|
|
27
|
+
}
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Read default data-store entries from the environment.
|
|
32
|
+
*
|
|
33
|
+
* Environment variables:
|
|
34
|
+
* - `SFNEXT_DATA_STORE_DEFAULTS` (optional): JSON map of keys to preference objects.
|
|
35
|
+
* Example: {"custom-global-preferences":{"featureFlag":true}}
|
|
36
|
+
*
|
|
37
|
+
* @returns Parsed defaults map or an empty object.
|
|
38
|
+
*/
|
|
39
|
+
function readDefaultsFromEnv() {
|
|
40
|
+
const raw = process.env.SFNEXT_DATA_STORE_DEFAULTS;
|
|
41
|
+
if (!raw) return {};
|
|
42
|
+
try {
|
|
43
|
+
const parsed = JSON.parse(raw);
|
|
44
|
+
if (parsed && typeof parsed === "object") return parsed;
|
|
45
|
+
} catch (error) {
|
|
46
|
+
console.warn("Failed to parse SFNEXT_DATA_STORE_DEFAULTS JSON.", error);
|
|
47
|
+
}
|
|
48
|
+
return {};
|
|
49
|
+
}
|
|
50
|
+
/**
|
|
51
|
+
* Read warn-on-missing behavior from the environment.
|
|
52
|
+
*
|
|
53
|
+
* Environment variables:
|
|
54
|
+
* - `SFNEXT_DATA_STORE_WARN_ON_MISSING` (optional): Set to "false" to silence warnings.
|
|
55
|
+
*
|
|
56
|
+
* @returns True when warnings should be emitted for missing entries.
|
|
57
|
+
*/
|
|
58
|
+
function readWarnOnMissingFromEnv() {
|
|
59
|
+
const raw = process.env.SFNEXT_DATA_STORE_WARN_ON_MISSING;
|
|
60
|
+
if (!raw) return true;
|
|
61
|
+
return raw.toLowerCase() !== "false";
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
//#endregion
|
|
65
|
+
export { createLocalDataStoreProvider };
|
|
66
|
+
//# sourceMappingURL=local-provider.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"local-provider.js","names":[],"sources":["../../src/data-store/local-provider.ts"],"sourcesContent":["/**\n * Copyright 2026 Salesforce, Inc.\n *\n * Licensed under the Apache License, Version 2.0 (the \"License\");\n * you may not use this file except in compliance with the License.\n * You may obtain a copy of the License at\n *\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Unless required by applicable law or agreed to in writing, software\n * distributed under the License is distributed on an \"AS IS\" BASIS,\n * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.\n * See the License for the specific language governing permissions and\n * limitations under the License.\n */\n\ntype DataStoreEntry = {\n value?: unknown;\n};\n\nexport type LocalDataStoreProvider = {\n kind: 'local';\n getEntry: (key: string) => Promise<DataStoreEntry | null>;\n};\n\ntype LocalDataStoreProviderOptions = {\n defaults?: Record<string, Record<string, unknown>>;\n warnOnMissing?: boolean;\n};\n\n/**\n * Create a local data-store provider for development environments.\n *\n * Environment variables:\n * - `SFNEXT_DATA_STORE_DEFAULTS` (optional): JSON map of keys to preference objects.\n * Example: {\"custom-global-preferences\":{\"featureFlag\":true}}\n * - `SFNEXT_DATA_STORE_WARN_ON_MISSING` (optional): Set to \"false\" to silence warnings.\n *\n * @param options - Optional defaults and warning controls for local entries.\n * @returns Local provider that supplies preferences from defaults or empty values.\n */\nexport function createLocalDataStoreProvider(options: LocalDataStoreProviderOptions = {}): LocalDataStoreProvider {\n const defaults = options.defaults ?? readDefaultsFromEnv();\n const warnOnMissing = options.warnOnMissing ?? readWarnOnMissingFromEnv();\n const warnedKeys = new Set<string>();\n\n return {\n kind: 'local',\n getEntry(key: string) {\n const value = defaults[key];\n if (value && typeof value === 'object') {\n return Promise.resolve({ value });\n }\n\n if (warnOnMissing && !warnedKeys.has(key)) {\n warnedKeys.add(key);\n // eslint-disable-next-line no-console\n console.warn(\n `Local data-store provider did not find '${key}'. Returning an empty object for development.`\n );\n }\n\n return Promise.resolve({ value: {} });\n },\n };\n}\n\n/**\n * Read default data-store entries from the environment.\n *\n * Environment variables:\n * - `SFNEXT_DATA_STORE_DEFAULTS` (optional): JSON map of keys to preference objects.\n * Example: {\"custom-global-preferences\":{\"featureFlag\":true}}\n *\n * @returns Parsed defaults map or an empty object.\n */\nfunction readDefaultsFromEnv(): Record<string, Record<string, unknown>> {\n const raw = process.env.SFNEXT_DATA_STORE_DEFAULTS;\n if (!raw) {\n return {};\n }\n\n try {\n const parsed = JSON.parse(raw);\n if (parsed && typeof parsed === 'object') {\n return parsed as Record<string, Record<string, unknown>>;\n }\n } catch (error) {\n // eslint-disable-next-line no-console\n console.warn('Failed to parse SFNEXT_DATA_STORE_DEFAULTS JSON.', error);\n }\n\n return {};\n}\n\n/**\n * Read warn-on-missing behavior from the environment.\n *\n * Environment variables:\n * - `SFNEXT_DATA_STORE_WARN_ON_MISSING` (optional): Set to \"false\" to silence warnings.\n *\n * @returns True when warnings should be emitted for missing entries.\n */\nfunction readWarnOnMissingFromEnv(): boolean {\n const raw = process.env.SFNEXT_DATA_STORE_WARN_ON_MISSING;\n if (!raw) {\n return true;\n }\n return raw.toLowerCase() !== 'false';\n}\n"],"mappings":";;;;;;;;;;;;AAyCA,SAAgB,6BAA6B,UAAyC,EAAE,EAA0B;CAC9G,MAAM,WAAW,QAAQ,YAAY,qBAAqB;CAC1D,MAAM,gBAAgB,QAAQ,iBAAiB,0BAA0B;CACzE,MAAM,6BAAa,IAAI,KAAa;AAEpC,QAAO;EACH,MAAM;EACN,SAAS,KAAa;GAClB,MAAM,QAAQ,SAAS;AACvB,OAAI,SAAS,OAAO,UAAU,SAC1B,QAAO,QAAQ,QAAQ,EAAE,OAAO,CAAC;AAGrC,OAAI,iBAAiB,CAAC,WAAW,IAAI,IAAI,EAAE;AACvC,eAAW,IAAI,IAAI;AAEnB,YAAQ,KACJ,2CAA2C,IAAI,+CAClD;;AAGL,UAAO,QAAQ,QAAQ,EAAE,OAAO,EAAE,EAAE,CAAC;;EAE5C;;;;;;;;;;;AAYL,SAAS,sBAA+D;CACpE,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,CAAC,IACD,QAAO,EAAE;AAGb,KAAI;EACA,MAAM,SAAS,KAAK,MAAM,IAAI;AAC9B,MAAI,UAAU,OAAO,WAAW,SAC5B,QAAO;UAEN,OAAO;AAEZ,UAAQ,KAAK,oDAAoD,MAAM;;AAG3E,QAAO,EAAE;;;;;;;;;;AAWb,SAAS,2BAAoC;CACzC,MAAM,MAAM,QAAQ,IAAI;AACxB,KAAI,CAAC,IACD,QAAO;AAEX,QAAO,IAAI,aAAa,KAAK"}
|
package/dist/flags.js
CHANGED
|
@@ -1,11 +1,13 @@
|
|
|
1
1
|
import { Flags } from "@oclif/core";
|
|
2
2
|
|
|
3
3
|
//#region src/flags.ts
|
|
4
|
-
const
|
|
5
|
-
|
|
4
|
+
const PROJECT_DIRECTORY_FLAG = "project-directory";
|
|
5
|
+
const PROJECT_DIRECTORY_CHAR = "d";
|
|
6
|
+
const commonFlags = { [PROJECT_DIRECTORY_FLAG]: Flags.string({
|
|
7
|
+
char: PROJECT_DIRECTORY_CHAR,
|
|
6
8
|
description: "Project directory",
|
|
7
9
|
default: process.cwd()
|
|
8
10
|
}) };
|
|
9
11
|
|
|
10
12
|
//#endregion
|
|
11
|
-
export { commonFlags as t };
|
|
13
|
+
export { PROJECT_DIRECTORY_FLAG as n, commonFlags as r, PROJECT_DIRECTORY_CHAR as t };
|
|
@@ -120,7 +120,7 @@ const SKIP_DIRECTORIES = [
|
|
|
120
120
|
".next",
|
|
121
121
|
"coverage"
|
|
122
122
|
];
|
|
123
|
-
const DEFAULT_COMPONENT_GROUP = "
|
|
123
|
+
const DEFAULT_COMPONENT_GROUP = "storefrontnext_base";
|
|
124
124
|
const ARCH_TYPE_HEADLESS = "headless";
|
|
125
125
|
const VALID_ATTRIBUTE_TYPES = [
|
|
126
126
|
"string",
|
|
@@ -617,6 +617,14 @@ async function generateMetadata(projectDirectory, metadataDirectory, options) {
|
|
|
617
617
|
}
|
|
618
618
|
};
|
|
619
619
|
await scanDirectory(srcDir);
|
|
620
|
+
const configMetadataDir = join(projectRoot, "config-metadata");
|
|
621
|
+
try {
|
|
622
|
+
await access(configMetadataDir);
|
|
623
|
+
await scanDirectory(configMetadataDir);
|
|
624
|
+
} catch (error) {
|
|
625
|
+
if (error.code === "ENOENT") logger.debug(` - Directory not found (skipping): ${configMetadataDir}`);
|
|
626
|
+
else logger.warn(` - Unable to access ${configMetadataDir}:`, error.message);
|
|
627
|
+
}
|
|
620
628
|
}
|
|
621
629
|
const allComponents = [];
|
|
622
630
|
const allPageTypes = [];
|