@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,191 @@
|
|
|
1
|
+
import { r as commonFlags } from "../../flags.js";
|
|
2
|
+
import { ux } from "@oclif/core";
|
|
3
|
+
import fs from "fs-extra";
|
|
4
|
+
import chalk from "chalk";
|
|
5
|
+
import { parseEnv } from "node:util";
|
|
6
|
+
import { join, resolve } from "node:path";
|
|
7
|
+
import { pathToFileURL } from "node:url";
|
|
8
|
+
import { MrtCommand } from "@salesforce/b2c-tooling-sdk/cli";
|
|
9
|
+
import { listEnvVars } from "@salesforce/b2c-tooling-sdk/operations/mrt";
|
|
10
|
+
|
|
11
|
+
//#region src/utils/objects.ts
|
|
12
|
+
/**
|
|
13
|
+
* Recursively flattens a nested object into dot-notation key-value pairs.
|
|
14
|
+
* Arrays and primitives are treated as leaf values (not further traversed).
|
|
15
|
+
*
|
|
16
|
+
* @param obj - The object to flatten
|
|
17
|
+
* @param prefix - Dot-notation prefix for nested keys
|
|
18
|
+
*/
|
|
19
|
+
function flattenObject(obj, prefix = "") {
|
|
20
|
+
const entries = [];
|
|
21
|
+
for (const [k, v] of Object.entries(obj)) {
|
|
22
|
+
const key = prefix ? `${prefix}.${k}` : k;
|
|
23
|
+
if (v !== null && typeof v === "object" && !Array.isArray(v)) entries.push(...flattenObject(v, key));
|
|
24
|
+
else entries.push({
|
|
25
|
+
key,
|
|
26
|
+
value: v
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
return entries;
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
//#endregion
|
|
33
|
+
//#region src/commands/config/inspect-utils.ts
|
|
34
|
+
/**
|
|
35
|
+
* Converts a PUBLIC__-prefixed env key to dot-notation config path.
|
|
36
|
+
* Example: PUBLIC__app__commerce__api__clientId → app.commerce.api.clientId
|
|
37
|
+
*/
|
|
38
|
+
function envKeyToConfigPath(envKey) {
|
|
39
|
+
return envKey.replace(/^PUBLIC__/, "").replace(/__/g, ".");
|
|
40
|
+
}
|
|
41
|
+
/**
|
|
42
|
+
* Converts a dot-notation config path to a PUBLIC__-prefixed env key.
|
|
43
|
+
* Example: app.commerce.api.clientId → PUBLIC__app__commerce__api__clientId
|
|
44
|
+
*/
|
|
45
|
+
function configPathToEnvKey(configPath) {
|
|
46
|
+
return `PUBLIC__${configPath.replace(/\./g, "__")}`;
|
|
47
|
+
}
|
|
48
|
+
/**
|
|
49
|
+
* Determines the source of each flattened config path.
|
|
50
|
+
* Returns ".env" when there is a corresponding PUBLIC__-prefixed key in envKeys;
|
|
51
|
+
* returns "config" otherwise.
|
|
52
|
+
*
|
|
53
|
+
* @param flattenedKeys - Array of dot-notation config paths
|
|
54
|
+
* @param envKeys - Set of PUBLIC__-prefixed keys found in .env
|
|
55
|
+
*/
|
|
56
|
+
function getValueSources(flattenedKeys, envKeys) {
|
|
57
|
+
const sources = /* @__PURE__ */ new Map();
|
|
58
|
+
for (const configPath of flattenedKeys) sources.set(configPath, envKeys.has(configPathToEnvKey(configPath)) ? ".env" : "config");
|
|
59
|
+
return sources;
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Formats the full inspection output as a string.
|
|
63
|
+
*/
|
|
64
|
+
function formatInspectOutput({ flatConfig, sources, localVars, mrtVars }) {
|
|
65
|
+
const lines = [];
|
|
66
|
+
const maxKeyLen = (keys) => keys.reduce((max, k) => Math.max(max, k.length), 0);
|
|
67
|
+
const envOverrides = flatConfig.filter((e) => sources.get(e.key) === ".env").sort((a, b) => a.key.localeCompare(b.key));
|
|
68
|
+
const totalCount = flatConfig.length;
|
|
69
|
+
if (totalCount > 0) lines.push(` Config: config.server.ts (${totalCount} values, ${envOverrides.length} overridden by .env)`);
|
|
70
|
+
else lines.push(chalk.dim(" (no config loaded)"));
|
|
71
|
+
lines.push("");
|
|
72
|
+
lines.push(chalk.bold("=== .env Overrides ==="));
|
|
73
|
+
lines.push(chalk.dim(" Config paths overridden by PUBLIC__ env vars in .env."));
|
|
74
|
+
lines.push("");
|
|
75
|
+
if (envOverrides.length === 0) lines.push(chalk.dim(" (no .env overrides)"));
|
|
76
|
+
else {
|
|
77
|
+
const padLen = maxKeyLen(envOverrides.map((e) => e.key));
|
|
78
|
+
for (const { key, value } of envOverrides) {
|
|
79
|
+
const tag = mrtVars !== null && !mrtVars.has(configPathToEnvKey(key)) ? ` ${chalk.yellow("[local only]")}` : "";
|
|
80
|
+
lines.push(` ${key.padEnd(padLen)} = ${JSON.stringify(value)}${tag}`);
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
lines.push("");
|
|
84
|
+
if (mrtVars !== null) {
|
|
85
|
+
lines.push(chalk.bold("=== MRT Overrides ==="));
|
|
86
|
+
lines.push(chalk.dim(" Config paths overridden by PUBLIC__ env vars in MRT. Values are masked by MRT."));
|
|
87
|
+
lines.push("");
|
|
88
|
+
const mrtPublicEntries = [...mrtVars.entries()].filter(([key]) => key.startsWith("PUBLIC__")).map(([key, value]) => [
|
|
89
|
+
key,
|
|
90
|
+
value,
|
|
91
|
+
envKeyToConfigPath(key)
|
|
92
|
+
]).sort(([, , configPath1], [, , configPath2]) => configPath1.localeCompare(configPath2));
|
|
93
|
+
if (mrtPublicEntries.length === 0) lines.push(chalk.dim(" (no MRT config overrides)"));
|
|
94
|
+
else {
|
|
95
|
+
const padLen = maxKeyLen(mrtPublicEntries.map(([, , configPath]) => configPath));
|
|
96
|
+
for (const [key, value, configPath] of mrtPublicEntries) {
|
|
97
|
+
const tag = !localVars.has(key) ? ` ${chalk.cyan("[MRT only]")}` : "";
|
|
98
|
+
lines.push(` ${configPath.padEnd(padLen)} = ${value}${tag}`);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
lines.push("");
|
|
102
|
+
}
|
|
103
|
+
if (envOverrides.length > 0) {
|
|
104
|
+
lines.push(chalk.dim(" Tip: Run `pnpm config:push-env` to sync local .env overrides to MRT."));
|
|
105
|
+
lines.push("");
|
|
106
|
+
}
|
|
107
|
+
return lines.join("\n");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
//#endregion
|
|
111
|
+
//#region src/commands/config/inspect.ts
|
|
112
|
+
/**
|
|
113
|
+
* Show which config.server.ts values are overridden by .env or MRT.
|
|
114
|
+
* When MRT is configured, each override is marked [local only] or [MRT only] as applicable.
|
|
115
|
+
*
|
|
116
|
+
* Environment variables read:
|
|
117
|
+
* MRT_PROJECT (optional) - MRT project slug, overridden by --project flag
|
|
118
|
+
* MRT_TARGET (optional) - MRT target environment, overridden by --environment flag
|
|
119
|
+
*/
|
|
120
|
+
var ConfigInspect = class ConfigInspect extends MrtCommand {
|
|
121
|
+
static description = "Show which config.server.ts values are overridden by .env or MRT";
|
|
122
|
+
static examples = [
|
|
123
|
+
"<%= config.bin %> <%= command.id %>",
|
|
124
|
+
"<%= config.bin %> <%= command.id %> --project my-project --environment staging",
|
|
125
|
+
"<%= config.bin %> <%= command.id %> -d /path/to/my-storefront"
|
|
126
|
+
];
|
|
127
|
+
static flags = {
|
|
128
|
+
...MrtCommand.baseFlags,
|
|
129
|
+
...commonFlags
|
|
130
|
+
};
|
|
131
|
+
operations = {
|
|
132
|
+
readEnvFile: (projectDirectory) => {
|
|
133
|
+
const envPath = join(projectDirectory, ".env");
|
|
134
|
+
try {
|
|
135
|
+
return parseEnv(fs.readFileSync(envPath, "utf8"));
|
|
136
|
+
} catch (err) {
|
|
137
|
+
if (err.code === "ENOENT") return {};
|
|
138
|
+
throw err;
|
|
139
|
+
}
|
|
140
|
+
},
|
|
141
|
+
loadConfig: async (projectDirectory) => {
|
|
142
|
+
return (await import(pathToFileURL(join(projectDirectory, "node_modules/@salesforce/storefront-next-runtime/dist/config-load.js")).href)).loadConfig();
|
|
143
|
+
},
|
|
144
|
+
listEnvVars
|
|
145
|
+
};
|
|
146
|
+
async run() {
|
|
147
|
+
const { flags, raw } = await this.parse(ConfigInspect);
|
|
148
|
+
const projectDirectory = resolve(flags["project-directory"]);
|
|
149
|
+
const explicitFlags = new Set(raw.filter((t) => t.type === "flag").map((t) => t.flag));
|
|
150
|
+
const rawEnvVars = this.operations.readEnvFile(projectDirectory);
|
|
151
|
+
if (Object.keys(rawEnvVars).length === 0) this.warn(`No .env file found in ${projectDirectory}. Showing config.server.ts defaults only.`);
|
|
152
|
+
let config = {};
|
|
153
|
+
const originalCwd = process.cwd();
|
|
154
|
+
try {
|
|
155
|
+
process.chdir(projectDirectory);
|
|
156
|
+
config = await this.operations.loadConfig(projectDirectory);
|
|
157
|
+
} catch (err) {
|
|
158
|
+
this.warn(`Could not load storefront config: ${err.message}`);
|
|
159
|
+
} finally {
|
|
160
|
+
process.chdir(originalCwd);
|
|
161
|
+
}
|
|
162
|
+
const flatConfig = flattenObject(config);
|
|
163
|
+
const envKeys = new Set(Object.keys(rawEnvVars).filter((k) => k.startsWith("PUBLIC__")));
|
|
164
|
+
const sources = getValueSources(flatConfig.map((e) => e.key), envKeys);
|
|
165
|
+
const project = (explicitFlags.has("project") ? flags.project : void 0) || rawEnvVars.MRT_PROJECT || this.resolvedConfig.values.mrtProject;
|
|
166
|
+
const environment = (explicitFlags.has("environment") ? flags.environment : void 0) || rawEnvVars.MRT_TARGET || this.resolvedConfig.values.mrtEnvironment;
|
|
167
|
+
let mrtVars = null;
|
|
168
|
+
if (project && environment) try {
|
|
169
|
+
this.requireMrtCredentials();
|
|
170
|
+
const { variables } = await this.operations.listEnvVars({
|
|
171
|
+
projectSlug: project,
|
|
172
|
+
environment,
|
|
173
|
+
origin: this.resolvedConfig.values.mrtOrigin
|
|
174
|
+
}, this.getMrtAuth());
|
|
175
|
+
mrtVars = new Map(variables.map((v) => [v.name, v.value]));
|
|
176
|
+
} catch (err) {
|
|
177
|
+
this.warn(`Could not fetch MRT env vars for ${project}/${environment}: ${err.message}`);
|
|
178
|
+
}
|
|
179
|
+
else ux.stdout("ℹ MRT project/environment not configured. Skipping MRT comparison.\n Use --project and --environment flags or set MRT_PROJECT/MRT_TARGET.\n");
|
|
180
|
+
const output = formatInspectOutput({
|
|
181
|
+
flatConfig,
|
|
182
|
+
sources,
|
|
183
|
+
localVars: new Map(Object.entries(rawEnvVars)),
|
|
184
|
+
mrtVars
|
|
185
|
+
});
|
|
186
|
+
ux.stdout(output);
|
|
187
|
+
}
|
|
188
|
+
};
|
|
189
|
+
|
|
190
|
+
//#endregion
|
|
191
|
+
export { ConfigInspect as default };
|
|
@@ -2,7 +2,7 @@ import { t as logger } from "../logger.js";
|
|
|
2
2
|
import "../logger2.js";
|
|
3
3
|
import { i as getMrtConfig, n as getDefaultBuildDir, r as getDefaultMessage } from "../utils.js";
|
|
4
4
|
import { a as buildMrtConfig } from "../config.js";
|
|
5
|
-
import {
|
|
5
|
+
import { r as commonFlags } from "../flags.js";
|
|
6
6
|
import { t as createBundle } from "../bundle.js";
|
|
7
7
|
import { Command, Flags } from "@oclif/core";
|
|
8
8
|
import path from "path";
|
|
@@ -7,8 +7,8 @@ import { Command, Flags } from "@oclif/core";
|
|
|
7
7
|
import { execFileSync, execSync } from "child_process";
|
|
8
8
|
import path from "path";
|
|
9
9
|
import fs from "fs-extra";
|
|
10
|
-
import dotenv from "dotenv";
|
|
11
10
|
import prompts from "prompts";
|
|
11
|
+
import { parseEnv } from "node:util";
|
|
12
12
|
|
|
13
13
|
//#region src/create-storefront.ts
|
|
14
14
|
const DEFAULT_STOREFRONT = "sfcc-storefront";
|
|
@@ -156,7 +156,7 @@ const createStorefront = async (options = {}) => {
|
|
|
156
156
|
const configMeta = JSON.parse(fs.readFileSync(configMetaPath, "utf8"));
|
|
157
157
|
const envDefaultPath = path.join(outputPath, ".env.default");
|
|
158
158
|
let envDefaultValues = {};
|
|
159
|
-
if (fs.existsSync(envDefaultPath)) envDefaultValues =
|
|
159
|
+
if (fs.existsSync(envDefaultPath)) envDefaultValues = parseEnv(fs.readFileSync(envDefaultPath, "utf8"));
|
|
160
160
|
logger.info("\n⚙️ We will now configure your storefront before it will be ready to run.\n");
|
|
161
161
|
const configOverrides = {};
|
|
162
162
|
for (const config of configMeta.configs) if (options.defaults) configOverrides[config.key] = envDefaultValues[config.key] ?? "";
|
|
@@ -164,7 +164,7 @@ const createStorefront = async (options = {}) => {
|
|
|
164
164
|
const answer = await prompts({
|
|
165
165
|
type: "text",
|
|
166
166
|
name: config.key,
|
|
167
|
-
message: `What is the value for ${config.name}? (default: ${envDefaultValues[config.key]})\n`,
|
|
167
|
+
message: `What is the value for ${config.name}? (default: ${envDefaultValues[config.key] ?? ""})\n`,
|
|
168
168
|
initial: envDefaultValues[config.key] ?? ""
|
|
169
169
|
});
|
|
170
170
|
configOverrides[config.key] = answer[config.key];
|
package/dist/commands/dev.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { i as printShutdownMessage, n as printServerConfig, r as printServerInfo } from "../logger.js";
|
|
2
2
|
import "../logger2.js";
|
|
3
|
-
import { c as loadEnvFile } from "../utils.js";
|
|
4
3
|
import { i as loadProjectConfig, n as initBasePathEnv, r as getCommerceCloudApiUrl, t as createServer$2 } from "../server.js";
|
|
5
4
|
import "../config.js";
|
|
6
|
-
import {
|
|
5
|
+
import { r as commonFlags } from "../flags.js";
|
|
7
6
|
import { Command, Flags } from "@oclif/core";
|
|
8
7
|
import path from "path";
|
|
9
8
|
import { createServer } from "node:http";
|
|
@@ -49,7 +48,6 @@ async function dev(options = {}) {
|
|
|
49
48
|
const projectDir = path.resolve(options.projectDirectory || process.cwd());
|
|
50
49
|
const port = options.port || 5173;
|
|
51
50
|
process.env.NODE_ENV = process.env.NODE_ENV ?? "development";
|
|
52
|
-
loadEnvFile(projectDir);
|
|
53
51
|
await initBasePathEnv(projectDir);
|
|
54
52
|
process.env.EXTERNAL_DOMAIN_NAME = process.env.EXTERNAL_DOMAIN_NAME ?? `localhost:${port}`;
|
|
55
53
|
const config = await loadProjectConfig(projectDir);
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../../logger.js";
|
|
2
2
|
import "../../logger2.js";
|
|
3
3
|
import "../../dependency-utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { r as commonFlags } from "../../flags.js";
|
|
5
5
|
import { r as manageExtensions } from "../../manage-extensions.js";
|
|
6
6
|
import { Command, Flags } from "@oclif/core";
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../../logger.js";
|
|
2
2
|
import "../../logger2.js";
|
|
3
3
|
import "../../dependency-utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { r as commonFlags } from "../../flags.js";
|
|
5
5
|
import { n as listExtensions } from "../../manage-extensions.js";
|
|
6
6
|
import { Command } from "@oclif/core";
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../../logger.js";
|
|
2
2
|
import "../../logger2.js";
|
|
3
3
|
import "../../dependency-utils.js";
|
|
4
|
-
import {
|
|
4
|
+
import { r as commonFlags } from "../../flags.js";
|
|
5
5
|
import { r as manageExtensions } from "../../manage-extensions.js";
|
|
6
6
|
import { Command, Flags } from "@oclif/core";
|
|
7
7
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import "../logger.js";
|
|
2
2
|
import "../logger2.js";
|
|
3
3
|
import { i as SFNEXT_BASE_CARTRIDGE_OUTPUT_DIR, t as CARTRIDGES_BASE_DIR } from "../config.js";
|
|
4
|
-
import {
|
|
4
|
+
import { r as commonFlags } from "../flags.js";
|
|
5
5
|
import { t as generateMetadata } from "../generate-cartridge.js";
|
|
6
6
|
import { t as validateCartridgeMetadata } from "../validate-cartridge.js";
|
|
7
7
|
import { Command } from "@oclif/core";
|
|
@@ -0,0 +1,181 @@
|
|
|
1
|
+
import { t as logger } from "../../logger.js";
|
|
2
|
+
import "../../logger2.js";
|
|
3
|
+
import { r as commonFlags } from "../../flags.js";
|
|
4
|
+
import { Command, Flags } from "@oclif/core";
|
|
5
|
+
import { existsSync } from "node:fs";
|
|
6
|
+
import { join } from "node:path";
|
|
7
|
+
import { mkdir, readdir, writeFile } from "fs/promises";
|
|
8
|
+
|
|
9
|
+
//#region src/i18n/aggregate-extension-locales.ts
|
|
10
|
+
/** Apache License 2.0 header text for generated files. Inlined to avoid path resolution issues in standalone projects. */
|
|
11
|
+
const APACHE_LICENSE_HEADER = [
|
|
12
|
+
`Copyright ${(/* @__PURE__ */ new Date()).getFullYear()} Salesforce, Inc.`,
|
|
13
|
+
"",
|
|
14
|
+
"Licensed under the Apache License, Version 2.0 (the \"License\");",
|
|
15
|
+
"you may not use this file except in compliance with the License.",
|
|
16
|
+
"You may obtain a copy of the License at",
|
|
17
|
+
"",
|
|
18
|
+
" http://www.apache.org/licenses/LICENSE-2.0",
|
|
19
|
+
"",
|
|
20
|
+
"Unless required by applicable law or agreed to in writing, software",
|
|
21
|
+
"distributed under the License is distributed on an \"AS IS\" BASIS,",
|
|
22
|
+
"WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.",
|
|
23
|
+
"See the License for the specific language governing permissions and",
|
|
24
|
+
"limitations under the License."
|
|
25
|
+
].join("\n");
|
|
26
|
+
function getDefaultDirs(projectDirectory) {
|
|
27
|
+
const srcDir = join(projectDirectory, "src");
|
|
28
|
+
return {
|
|
29
|
+
SRC_DIR: srcDir,
|
|
30
|
+
EXTENSIONS_DIR: join(srcDir, "extensions"),
|
|
31
|
+
OUTPUT_DIR: join(srcDir, "extensions", "locales")
|
|
32
|
+
};
|
|
33
|
+
}
|
|
34
|
+
/** Convert kebab-case to PascalCase (e.g. `store-locator` → `StoreLocator`). */
|
|
35
|
+
function toPascalCase(str) {
|
|
36
|
+
return str.split("-").map((part) => part.charAt(0).toUpperCase() + part.slice(1)).join("");
|
|
37
|
+
}
|
|
38
|
+
/** Convert kebab-case to camelCase for variable names (e.g. `store-locator` → `storeLocator`). */
|
|
39
|
+
function toCamelCase(str) {
|
|
40
|
+
const pascal = toPascalCase(str);
|
|
41
|
+
return pascal.charAt(0).toLowerCase() + pascal.slice(1);
|
|
42
|
+
}
|
|
43
|
+
/** Scan main app and extension directories to find all available locale codes. */
|
|
44
|
+
async function discoverLocales(dirs) {
|
|
45
|
+
const { SRC_DIR, EXTENSIONS_DIR } = dirs;
|
|
46
|
+
const locales = /* @__PURE__ */ new Set();
|
|
47
|
+
const mainLocalesPath = join(SRC_DIR, "locales");
|
|
48
|
+
if (existsSync(mainLocalesPath)) try {
|
|
49
|
+
const mainLocaleEntries = await readdir(mainLocalesPath, { withFileTypes: true });
|
|
50
|
+
for (const entry of mainLocaleEntries) if (entry.isDirectory()) locales.add(entry.name);
|
|
51
|
+
} catch (error) {
|
|
52
|
+
if (error.code !== "ENOENT") throw error;
|
|
53
|
+
}
|
|
54
|
+
try {
|
|
55
|
+
const extensions = await readdir(EXTENSIONS_DIR, { withFileTypes: true });
|
|
56
|
+
for (const extension of extensions) {
|
|
57
|
+
if (!extension.isDirectory() || extension.name === "locales") continue;
|
|
58
|
+
const localesPath = join(EXTENSIONS_DIR, extension.name, "locales");
|
|
59
|
+
if (!existsSync(localesPath)) continue;
|
|
60
|
+
const localeEntries = await readdir(localesPath, { withFileTypes: true });
|
|
61
|
+
for (const localeEntry of localeEntries) if (localeEntry.isDirectory()) locales.add(localeEntry.name);
|
|
62
|
+
}
|
|
63
|
+
} catch (error) {
|
|
64
|
+
if (error.code !== "ENOENT") throw error;
|
|
65
|
+
}
|
|
66
|
+
return locales;
|
|
67
|
+
}
|
|
68
|
+
/** Find all extensions (NOT main app) that have a `translations.json` for the given locale. */
|
|
69
|
+
async function findExtensionsWithLocale(locale, extensionsDir) {
|
|
70
|
+
const extensions = [];
|
|
71
|
+
try {
|
|
72
|
+
const extensionEntries = await readdir(extensionsDir, { withFileTypes: true });
|
|
73
|
+
for (const entry of extensionEntries) {
|
|
74
|
+
if (!entry.isDirectory() || entry.name === "locales") continue;
|
|
75
|
+
if (existsSync(join(extensionsDir, entry.name, "locales", locale, "translations.json"))) extensions.push({
|
|
76
|
+
name: entry.name,
|
|
77
|
+
path: `@/extensions/${entry.name}/locales/${locale}/translations.json`
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
} catch (error) {
|
|
81
|
+
if (error.code !== "ENOENT") throw error;
|
|
82
|
+
}
|
|
83
|
+
return extensions.sort((a, b) => a.name.localeCompare(b.name));
|
|
84
|
+
}
|
|
85
|
+
/** Generate the locale index file content that re-exports extension translations under `extPascalCase` namespaces. */
|
|
86
|
+
function generateLocaleFile(extensions) {
|
|
87
|
+
const header = `${`/**\n${APACHE_LICENSE_HEADER.split("\n").map((line) => line ? ` * ${line}` : " *").join("\n")}\n */`}
|
|
88
|
+
|
|
89
|
+
// NOTE: This file is auto-generated. Do not edit manually.
|
|
90
|
+
// Run 'pnpm locales:aggregate-extensions' to regenerate this file.
|
|
91
|
+
|
|
92
|
+
`;
|
|
93
|
+
if (extensions.length === 0) return `${header}// No extension translations found for this locale\nexport default {};\n`;
|
|
94
|
+
return `${header}${extensions.map((ext) => {
|
|
95
|
+
return `import ${`${toCamelCase(ext.name)}Translations`} from '${ext.path}';`;
|
|
96
|
+
}).join("\n")}
|
|
97
|
+
|
|
98
|
+
// Namespace is based on the following convention: extPascalCase, and it's the pascal case of the folder name (e.g. store-locator -> extStoreLocator)
|
|
99
|
+
export default {
|
|
100
|
+
${extensions.map((ext) => {
|
|
101
|
+
return ` ${`ext${toPascalCase(ext.name)}`}: ${`${toCamelCase(ext.name)}Translations`},`;
|
|
102
|
+
}).join("\n")}
|
|
103
|
+
};
|
|
104
|
+
`;
|
|
105
|
+
}
|
|
106
|
+
/**
|
|
107
|
+
* Generate aggregation files for extension translations only.
|
|
108
|
+
* Main app translations in `/src/locales/` are NOT aggregated — only per-extension `translations.json` files.
|
|
109
|
+
*/
|
|
110
|
+
async function aggregateExtensionLocales(options = {}) {
|
|
111
|
+
const { projectDirectory = process.cwd(), silent = false } = options;
|
|
112
|
+
const dirs = options.dirs ?? getDefaultDirs(projectDirectory);
|
|
113
|
+
const { OUTPUT_DIR, EXTENSIONS_DIR } = dirs;
|
|
114
|
+
const log = (message, ...args) => {
|
|
115
|
+
if (!silent) logger.debug(message, ...args);
|
|
116
|
+
};
|
|
117
|
+
try {
|
|
118
|
+
log("🔍 Scanning for extension translation files...");
|
|
119
|
+
const locales = await discoverLocales(dirs);
|
|
120
|
+
if (locales.size === 0) {
|
|
121
|
+
log("📝 No locales found in extensions. Nothing to generate.");
|
|
122
|
+
return {
|
|
123
|
+
generated: 0,
|
|
124
|
+
locales: []
|
|
125
|
+
};
|
|
126
|
+
}
|
|
127
|
+
log(`📝 Found ${locales.size} locale(s): ${Array.from(locales).join(", ")}`);
|
|
128
|
+
await mkdir(OUTPUT_DIR, { recursive: true });
|
|
129
|
+
const results = [];
|
|
130
|
+
for (const locale of locales) {
|
|
131
|
+
const extensions = await findExtensionsWithLocale(locale, EXTENSIONS_DIR);
|
|
132
|
+
const content = generateLocaleFile(extensions);
|
|
133
|
+
const outputPath = join(OUTPUT_DIR, locale);
|
|
134
|
+
await mkdir(outputPath, { recursive: true });
|
|
135
|
+
const filePath = join(outputPath, "index.ts");
|
|
136
|
+
await writeFile(filePath, content, "utf8");
|
|
137
|
+
log(`✅ Generated: src/extensions/locales/${locale}/index.ts (${extensions.length} extension(s))`);
|
|
138
|
+
results.push({
|
|
139
|
+
locale,
|
|
140
|
+
extensionCount: extensions.length,
|
|
141
|
+
filePath
|
|
142
|
+
});
|
|
143
|
+
}
|
|
144
|
+
log("✨ Extension locale generation complete!");
|
|
145
|
+
return {
|
|
146
|
+
generated: results.length,
|
|
147
|
+
locales: results
|
|
148
|
+
};
|
|
149
|
+
} catch (error) {
|
|
150
|
+
if (!silent) logger.error(`❌ Error generating extension locales: ${String(error)}`);
|
|
151
|
+
throw error;
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
//#endregion
|
|
156
|
+
//#region src/commands/locales/aggregate-extensions.ts
|
|
157
|
+
var AggregateExtensions = class AggregateExtensions extends Command {
|
|
158
|
+
static description = "Aggregate extension translation files into per-locale barrel files";
|
|
159
|
+
static examples = [
|
|
160
|
+
"<%= config.bin %> <%= command.id %>",
|
|
161
|
+
"<%= config.bin %> <%= command.id %> -d ./my-project",
|
|
162
|
+
"<%= config.bin %> <%= command.id %> --silent"
|
|
163
|
+
];
|
|
164
|
+
static flags = {
|
|
165
|
+
...commonFlags,
|
|
166
|
+
silent: Flags.boolean({
|
|
167
|
+
description: "Suppress output",
|
|
168
|
+
default: false
|
|
169
|
+
})
|
|
170
|
+
};
|
|
171
|
+
async run() {
|
|
172
|
+
const { flags } = await this.parse(AggregateExtensions);
|
|
173
|
+
await aggregateExtensionLocales({
|
|
174
|
+
projectDirectory: flags["project-directory"],
|
|
175
|
+
silent: flags.silent
|
|
176
|
+
});
|
|
177
|
+
}
|
|
178
|
+
};
|
|
179
|
+
|
|
180
|
+
//#endregion
|
|
181
|
+
export { AggregateExtensions as default };
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import "../logger.js";
|
|
2
2
|
import { t as prepareForLocalDev } from "../local-dev-setup.js";
|
|
3
|
-
import {
|
|
3
|
+
import { r as commonFlags } from "../flags.js";
|
|
4
4
|
import { Command, Flags } from "@oclif/core";
|
|
5
5
|
|
|
6
6
|
//#region src/commands/prepare-local.ts
|
package/dist/commands/preview.js
CHANGED
|
@@ -1,9 +1,8 @@
|
|
|
1
1
|
import { i as printShutdownMessage, n as printServerConfig, r as printServerInfo, t as logger } from "../logger.js";
|
|
2
2
|
import "../logger2.js";
|
|
3
|
-
import { c as loadEnvFile } from "../utils.js";
|
|
4
3
|
import { i as loadProjectConfig, n as initBasePathEnv, r as getCommerceCloudApiUrl, t as createServer } from "../server.js";
|
|
5
4
|
import "../config.js";
|
|
6
|
-
import {
|
|
5
|
+
import { r as commonFlags } from "../flags.js";
|
|
7
6
|
import { Command, Flags } from "@oclif/core";
|
|
8
7
|
import { execSync } from "child_process";
|
|
9
8
|
import path from "path";
|
|
@@ -22,7 +21,6 @@ async function preview(options = {}) {
|
|
|
22
21
|
const port = options.port || 3e3;
|
|
23
22
|
process.env.NODE_ENV = process.env.NODE_ENV ?? "production";
|
|
24
23
|
process.env.EXTERNAL_DOMAIN_NAME = process.env.EXTERNAL_DOMAIN_NAME ?? `localhost:${port}`;
|
|
25
|
-
loadEnvFile(projectDir);
|
|
26
24
|
await initBasePathEnv(projectDir);
|
|
27
25
|
const buildPath = path.join(projectDir, "build", "server", "index.js");
|
|
28
26
|
if (!fs.existsSync(buildPath)) {
|