@salesforce/storefront-next-dev 1.5.0-alpha.0 → 1.5.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/dist/server.js CHANGED
@@ -205,6 +205,35 @@ const SKIP_PATTERNS = [
205
205
  "**/*.css.map"
206
206
  ];
207
207
  /**
208
+ * Query parameters whose values are masked in request logs. Passwordless and password-reset
209
+ * links carry single-use tokens and, in older or merchant-configured links, the shopper email.
210
+ */
211
+ const REDACTED_LOG_QUERY_PARAMS = Object.freeze([
212
+ "token",
213
+ "otpCode",
214
+ "email",
215
+ "login_id"
216
+ ]);
217
+ /**
218
+ * Masks the values of sensitive query parameters in a request URL so they are not written to
219
+ * request logs. Other parameters are left untouched.
220
+ */
221
+ function redactUrlForLogging(url) {
222
+ if (!url) return url;
223
+ const queryIndex = url.indexOf("?");
224
+ if (queryIndex === -1) return url;
225
+ const query = url.slice(queryIndex + 1).split("&").map((pair) => {
226
+ const separatorIndex = pair.indexOf("=");
227
+ const rawKey = separatorIndex === -1 ? pair : pair.slice(0, separatorIndex);
228
+ let key = rawKey;
229
+ try {
230
+ key = decodeURIComponent(rawKey);
231
+ } catch {}
232
+ return REDACTED_LOG_QUERY_PARAMS.includes(key) ? `${rawKey}=[REDACTED]` : pair;
233
+ }).join("&");
234
+ return `${url.slice(0, queryIndex)}?${query}`;
235
+ }
236
+ /**
208
237
  * Create request logging middleware
209
238
  * Used in dev and preview modes for request visibility
210
239
  */
@@ -231,7 +260,7 @@ function createLoggingMiddleware() {
231
260
  return morgan((tokens, req, res) => {
232
261
  return [
233
262
  tokens["method-colored"](req, res),
234
- tokens.url(req, res),
263
+ redactUrlForLogging(tokens.url(req, res)),
235
264
  "-",
236
265
  tokens["status-colored"](req, res),
237
266
  chalk.gray(`(${tokens["response-time"](req, res)}ms)`)
package/dist/server2.js CHANGED
@@ -1,8 +1,7 @@
1
1
  import "./package.js";
2
- import { t as AUDIT_ROOT } from "./assets.js";
3
- import "./sanitize.js";
2
+ import { f as RUN_ID, h as runDir, m as listRuns, s as preparedSchema, t as LIGHTHOUSE_REPORT, x as AUDIT_ROOT } from "./results.js";
4
3
  import { i as BUNDLE_VISUALIZER } from "./schema.js";
5
- import { b as listRuns, n as deriveStatus, o as readEvents, p as preparedSchema, r as endsRun, s as LIGHTHOUSE_REPORT, v as RUN_ID, x as runDir } from "./events.js";
4
+ import { n as deriveStatus, o as readEvents, r as endsRun } from "./events.js";
6
5
  import { constants } from "node:fs";
7
6
  import path from "node:path";
8
7
  import { access, readFile, rm } from "node:fs/promises";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@salesforce/storefront-next-dev",
3
- "version": "1.5.0-alpha.0",
3
+ "version": "1.5.0-alpha.1",
4
4
  "description": "Dev and build tools for SFCC Storefront Next",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -187,7 +187,7 @@
187
187
  "@opentelemetry/sdk-trace-node": "^1.30.0",
188
188
  "@opentelemetry/semantic-conventions": "^1.28.0",
189
189
  "@react-router/express": "7.18.2",
190
- "@salesforce/b2c-tooling-sdk": "2.11.0",
190
+ "@salesforce/b2c-tooling-sdk": "2.14.0",
191
191
  "@salesforce/playhouse": "0.1.2",
192
192
  "ajv": "8.20.0",
193
193
  "archiver": "^7.0.1",
package/dist/assets.js DELETED
@@ -1,25 +0,0 @@
1
- import { t as version } from "./package.js";
2
- import { existsSync } from "node:fs";
3
- import path from "node:path";
4
- import { fileURLToPath } from "node:url";
5
-
6
- //#region src/audit/assets.ts
7
- /** The storefront-next-dev version, not the host CLI's: under `b2c sfnext`, oclif's config version is b2c's. */
8
- const VERSION = version;
9
- function packageRoot(from) {
10
- for (let dir = from;; dir = path.dirname(dir)) {
11
- if (existsSync(path.join(dir, "package.json"))) return dir;
12
- if (path.dirname(dir) === dir) throw new Error(`No package.json above ${from}`);
13
- }
14
- }
15
- const here = path.dirname(fileURLToPath(import.meta.url));
16
- const root = packageRoot(here);
17
- /**
18
- * The directory holding the audit's runtime assets (catalog, instructions, UI). tsdown moves shared code into
19
- * chunks at the top of `dist`, so paths relative to a module break after bundling. This resolves to `src/audit`
20
- * when running from source (tests) and `dist/audit` when running the build.
21
- */
22
- const AUDIT_ROOT = path.join(root, path.relative(root, here).split(path.sep)[0] === "src" ? "src" : "dist", "audit");
23
-
24
- //#endregion
25
- export { VERSION as n, AUDIT_ROOT as t };
package/dist/sanitize.js DELETED
@@ -1,185 +0,0 @@
1
- //#region src/audit/sanitize.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
- const bearer = /\bBearer\s+[A-Za-z0-9._~+/-]+=*/gi;
18
- const secretKey = new RegExp(`\\b([\\w-]{0,64}?)(?:api[_-]?key|api[_-]?signature|access[_-]?token|token|secret|password|passwd|authorization|set-cookie|cookie|session[_-]?id|sessionid|credentials?|csrf[_-]?token|private[_-]?key)(?:[_-]?(?:access[_-]?)?(?:key|value|hash|id|token))?\\\\?["']?\\s*[:=]\\s*`, "gi");
19
- const knownToken = /(?<![\w-])eyJ[\w-]{1,1024}\.eyJ[\w-]{1,16384}\.[\w-]*|\b[rs]k_(?:live|test)_[A-Za-z0-9]{10,}|\bwhsec_[A-Za-z0-9]{20,}|\bgh[opsru]_[A-Za-z0-9]{20,}|\bgithub_pat_\w{20,}|\bnpm_[A-Za-z0-9]{36}\b|\bx(?:ox[abeprs]|app)-(?![\w-]{0,253}\.[A-Za-z])[A-Za-z0-9-]{10,}|\bsk-(?:ant|proj)-(?![\w-]{0,253}\.[A-Za-z])[\w-]{20,}|\b(?:AKIA|ASIA)[0-9A-Z]{16}\b|(?<=hooks\.slack\.com\/services\/)[A-Za-z0-9/]+/g;
20
- const pemBegin = /-----BEGIN [A-Z ]{0,40}PRIVATE KEY-----/;
21
- const pemEnd = /-----END [A-Z ]{0,40}PRIVATE KEY-----/;
22
- const pemLine = /(?:[ \t]*(?:\r?\n|(?:\\r)?\\n))*[ \t]*(?:(?:Proc-Type|DEK-Info):[^\r\n\\]*|[A-Za-z0-9+/=]{16,})/y;
23
- const maxKeyLength = 16384;
24
- /** Not after `@` or `~`: `@/components/header` and `~/lib` are import aliases, not files on disk. */
25
- const absolutePath = /(?<![\w.@~-])(?:[A-Za-z]:[\\/]|\/)(?:[^\s<>'"`]+[\\/])+[^\s<>'"`,.;:]*/g;
26
- const webUrl = /https?:\/\/[^\s<>'"`]+/gi;
27
- /** Returns the first match at or after an index. Indexes must not decrease; reusing the last match keeps repeated searches linear. */
28
- function finder(pattern, text) {
29
- const global = new RegExp(pattern.source, "g");
30
- let found;
31
- return (from) => {
32
- if (found === void 0 || found && found.index < from) {
33
- global.lastIndex = from;
34
- found = global.exec(text);
35
- }
36
- return found;
37
- };
38
- }
39
- /** Redacts each private key from its BEGIN line to its END line. A key without an END line before the next key
40
- * (or within maxKeyLength) is redacted up to its last key-like line, so prose after a lone header stays. */
41
- function redactPrivateKeys(text) {
42
- const nextBegin = finder(pemBegin, text);
43
- const nextEnd = finder(pemEnd, text);
44
- let out = "";
45
- let last = 0;
46
- for (let begin = nextBegin(0); begin; begin = nextBegin(last)) {
47
- const start = begin.index + begin[0].length;
48
- const end = nextEnd(start);
49
- const following = nextBegin(start);
50
- let stop = start;
51
- if (end && end.index - start <= maxKeyLength && (!following || following.index > end.index)) stop = end.index + end[0].length;
52
- else for (pemLine.lastIndex = stop; pemLine.exec(text); pemLine.lastIndex = stop) stop = pemLine.lastIndex;
53
- out += `${text.slice(last, begin.index)}[REDACTED]`;
54
- last = stop;
55
- }
56
- return out + text.slice(last);
57
- }
58
- const quoteAt = (text, index) => {
59
- if (text[index] === "\"" || text[index] === "'") return text[index];
60
- return text[index] === "\\" && (text[index + 1] === "\"" || text[index + 1] === "'") ? text.slice(index, index + 2) : "";
61
- };
62
- const lineEnd = (text, index) => {
63
- const end = text.indexOf("\n", index);
64
- return end === -1 ? text.length : end;
65
- };
66
- const skip = (text, index, pattern) => {
67
- while (index < text.length && pattern.test(text.charAt(index))) index++;
68
- return index;
69
- };
70
- const wordChar = /[^\s"',;}&\\]/;
71
- const tokenChar = /[\w.~+/=-]/;
72
- const schemeCredential = /[ \t]+[^\s"',;}&\\]+/y;
73
- /**
74
- * Reads the value that starts at `start`: a quoted string up to its unescaped closing quote, a list or object up
75
- * to its matching bracket (plus any token glued to it), an auth scheme with its credential, or one word. An
76
- * unclosed quote or bracket takes the rest of the line. `core` is the value without its quotes.
77
- */
78
- function scanValue(text, start) {
79
- const quote = quoteAt(text, start);
80
- if (quote) {
81
- for (let index = start + quote.length; index < text.length && text[index] !== "\n"; index += text[index] === "\\" && text[index + 1] !== "\n" ? 2 : 1) if (text.startsWith(quote, index)) return {
82
- end: index + quote.length,
83
- quote,
84
- core: text.slice(start + quote.length, index)
85
- };
86
- } else if (text[start] === "[" || text[start] === "{") {
87
- let depth = 0;
88
- let inQuote = "";
89
- for (let index = start; index < text.length && text[index] !== "\n"; index++) {
90
- if (inQuote) {
91
- if (text.startsWith(inQuote, index)) {
92
- index += inQuote.length - 1;
93
- inQuote = "";
94
- } else if (text[index] === "\\") index++;
95
- continue;
96
- }
97
- inQuote = quoteAt(text, index);
98
- if (inQuote) index += inQuote.length - 1;
99
- else if (text[index] === "[" || text[index] === "{") depth++;
100
- else if ((text[index] === "]" || text[index] === "}") && --depth === 0) {
101
- const end$1 = skip(text, index + 1, tokenChar);
102
- return {
103
- end: end$1,
104
- quote: "",
105
- core: text.slice(start, end$1)
106
- };
107
- }
108
- }
109
- } else {
110
- let end$1 = skip(text, start, wordChar);
111
- if (/^(?:Basic|Bearer|Digest|Token)$/i.test(text.slice(start, end$1))) {
112
- schemeCredential.lastIndex = end$1;
113
- if (schemeCredential.exec(text)) end$1 = schemeCredential.lastIndex;
114
- }
115
- return {
116
- end: end$1,
117
- quote: "",
118
- core: text.slice(start, end$1)
119
- };
120
- }
121
- const end = lineEnd(text, start);
122
- return {
123
- end,
124
- quote: "",
125
- core: text.slice(start, end)
126
- };
127
- }
128
- /** Values that can't be secrets: an exact placeholder, booleans and null under any name (`hasPassword: true`),
129
- * and numbers under a page, count or limit name (`pageToken=2`). */
130
- function keepValue(prefix, value) {
131
- return /^(?:\[REDACTED\]|(?:Basic|Bearer|Digest|Token) \[REDACTED\]|true|false|null|undefined)$/i.test(value) || /^-?\d+(?:\.\d+)?$/.test(value) && /page|max|min|count|limit|total|num/i.test(prefix);
132
- }
133
- /** Redacts the value of every secret-named assignment. Each value is read and decided on once, and the search
134
- * resumes after it, so a value is never rewritten twice and the work stays linear. */
135
- function redactAssignments(text) {
136
- let out = "";
137
- let last = 0;
138
- secretKey.lastIndex = 0;
139
- for (let key = secretKey.exec(text); key; key = secretKey.exec(text)) {
140
- const start = key.index + key[0].length;
141
- const { end, quote, core } = scanValue(text, start);
142
- if (end === start) continue;
143
- if (!keepValue(key[1] ?? "", core)) {
144
- const scheme = /^(Basic|Bearer|Digest|Token)\s+\S/i.exec(core)?.[1];
145
- out += `${text.slice(last, start)}${quote}${scheme ? `${scheme} ` : ""}[REDACTED]${quote}`;
146
- last = end;
147
- }
148
- secretKey.lastIndex = end;
149
- }
150
- return out + text.slice(last);
151
- }
152
- function sanitizeUrl(value) {
153
- const parsed = new URL(value);
154
- if (parsed.protocol !== "https:" && parsed.protocol !== "http:") throw new Error("URL must use https (or http with --allow-http)");
155
- parsed.username = "";
156
- parsed.password = "";
157
- for (const key of [...parsed.searchParams.keys()]) parsed.searchParams.set(key, "[REDACTED]");
158
- parsed.hash = "";
159
- return parsed.toString();
160
- }
161
- function sanitizeText(value) {
162
- const urls = [];
163
- let sanitized = redactPrivateKeys(value.replaceAll("\0", "")).replace(knownToken, "[REDACTED]").replace(webUrl, (url) => {
164
- try {
165
- urls.push(sanitizeUrl(url));
166
- return `\u0000URL${urls.length - 1}\u0000`;
167
- } catch {
168
- return "[REDACTED_URL]";
169
- }
170
- });
171
- sanitized = redactAssignments(sanitized.replace(bearer, "Bearer [REDACTED]")).replace(absolutePath, "[REDACTED_PATH]");
172
- return sanitized.replace(/\u0000URL(\d+)\u0000/g, (match, index) => urls[Number(index)] ?? match);
173
- }
174
- function sanitizeJsonValue(value, key) {
175
- if (typeof value === "string") {
176
- if (key === "pointer" && value.startsWith("/")) return value.split("/").map((token, index) => index === 0 ? "" : sanitizeText(token)).join("/");
177
- return sanitizeText(value);
178
- }
179
- if (Array.isArray(value)) return value.map((item) => sanitizeJsonValue(item));
180
- if (value !== null && typeof value === "object") return Object.fromEntries(Object.entries(value).map(([childKey, child]) => [childKey, sanitizeJsonValue(child, childKey)]));
181
- return value;
182
- }
183
-
184
- //#endregion
185
- export { sanitizeText as n, sanitizeUrl as r, sanitizeJsonValue as t };