@sudajs/cli 0.13.4 → 0.15.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.d.ts +11 -1
- package/dist/index.js +324 -94
- package/dist/index.js.map +1 -1
- package/package.json +2 -2
- package/templates/theme/AGENTS.md +25 -5
- package/templates/theme/README.md +2 -2
- package/templates/theme/src/locales/en.content.json +94 -0
- package/templates/theme/src/locales/zh-CN.content.json +94 -0
- package/templates/theme/src/manifest.ts +1 -1
- package/templates/theme/src/preview-i18n.ts +13 -0
- package/templates/theme/src/templates.ts +55 -45
- package/templates/theme/src/runtime.client.ts +0 -5
package/dist/index.d.ts
CHANGED
|
@@ -1702,6 +1702,7 @@ declare function fetchJson<T>(url: string, options?: RequestInit & {
|
|
|
1702
1702
|
declare function validateThemeTailwindContract(root: string): Promise<void>;
|
|
1703
1703
|
declare function validateTheme(root: string): Promise<ValidatedTheme>;
|
|
1704
1704
|
declare function validateThemeLocales(root: string): Promise<void>;
|
|
1705
|
+
declare function validateThemePreviewLocales(root: string, previewLocales: string[] | undefined): Promise<void>;
|
|
1705
1706
|
declare function toViteModuleUrl(root: string, absolutePath: string): string;
|
|
1706
1707
|
declare function buildTheme(root: string): Promise<ValidatedTheme>;
|
|
1707
1708
|
declare function finalizeTheme(root: string): Promise<ValidatedTheme>;
|
|
@@ -1728,11 +1729,20 @@ declare const __testUtils: {
|
|
|
1728
1729
|
validateThemeName: typeof validateThemeName;
|
|
1729
1730
|
validateThemeTailwindContract: typeof validateThemeTailwindContract;
|
|
1730
1731
|
validateThemeLocales: typeof validateThemeLocales;
|
|
1732
|
+
validateThemePreviewLocales: typeof validateThemePreviewLocales;
|
|
1733
|
+
resolveThemePreviewLocale: typeof resolveThemePreviewLocale;
|
|
1734
|
+
withSerializedVitePreviewLocaleLoad: typeof withSerializedVitePreviewLocaleLoad;
|
|
1731
1735
|
toViteModuleUrl: typeof toViteModuleUrl;
|
|
1732
1736
|
updateRemotePageDraft: typeof updateRemotePageDraft;
|
|
1733
1737
|
};
|
|
1734
1738
|
declare function watchTheme(root: string, port: number): Promise<void>;
|
|
1739
|
+
declare function withSerializedVitePreviewLocaleLoad<Result>(server: object, load: () => Promise<Result>): Promise<Result>;
|
|
1735
1740
|
declare function resolveDevStarterSlug(pathname: string): string | null | undefined;
|
|
1741
|
+
declare function resolveThemePreviewLocale(params: {
|
|
1742
|
+
previewLocales: string[] | undefined;
|
|
1743
|
+
explicitLang?: string | null;
|
|
1744
|
+
acceptLanguage?: string;
|
|
1745
|
+
}): string;
|
|
1736
1746
|
/**
|
|
1737
1747
|
* Theme-project-scoped renderer bundle. We deliberately resolve `react`,
|
|
1738
1748
|
* `react-dom/server` and `@sudajs/theme-engine/server` through the theme
|
|
@@ -1751,7 +1761,7 @@ interface ThemeRenderer {
|
|
|
1751
1761
|
renderToString: typeof renderToString;
|
|
1752
1762
|
}
|
|
1753
1763
|
declare function loadThemeScopedRenderer(themeRoot: string): Promise<ThemeRenderer>;
|
|
1754
|
-
declare function renderDevStarterPageHtml(theme: ValidatedTheme, page: ThemeStarterPage, renderer: ThemeRenderer): string;
|
|
1764
|
+
declare function renderDevStarterPageHtml(theme: ValidatedTheme, page: ThemeStarterPage, renderer: ThemeRenderer, locale?: string, preserveLang?: boolean): string;
|
|
1755
1765
|
interface ScreenshotOptions extends ThemeRootOptions {
|
|
1756
1766
|
device?: string[] | string;
|
|
1757
1767
|
output?: string;
|
package/dist/index.js
CHANGED
|
@@ -9,7 +9,7 @@ import { fileURLToPath, pathToFileURL } from 'url';
|
|
|
9
9
|
import { McpServer } from '@modelcontextprotocol/sdk/server/mcp.js';
|
|
10
10
|
import { StdioServerTransport } from '@modelcontextprotocol/sdk/server/stdio.js';
|
|
11
11
|
import { themeDesignSystemSchema, createThemeAgentManifest, createSudaPageAgentRuntime, checkThemeModule, formatThemeCheckResult, agentValidationResultSchema, agentPageSchemaOutputSchema, agentComponentOutputSchema } from '@sudajs/theme-engine';
|
|
12
|
-
import { themeManifestSchema } from '@sudajs/theme-engine/server';
|
|
12
|
+
import { themeManifestSchema, runWithThemePreviewLocale } from '@sudajs/theme-engine/server';
|
|
13
13
|
import { Command } from 'commander';
|
|
14
14
|
import { build } from 'esbuild';
|
|
15
15
|
import { z } from 'zod';
|
|
@@ -580,85 +580,105 @@ export const { createRoot, hydrateRoot, version } = ReactDOMClient;
|
|
|
580
580
|
}
|
|
581
581
|
};
|
|
582
582
|
}
|
|
583
|
-
function
|
|
583
|
+
function createRejectEditorRuntimeDependencyPlugin() {
|
|
584
584
|
return {
|
|
585
|
-
name: "suda-
|
|
585
|
+
name: "suda-reject-editor-runtime-dependency",
|
|
586
586
|
setup(build) {
|
|
587
|
-
build.onResolve({ filter: /^@puckeditor\/core
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
contents: `
|
|
594
|
-
const PuckCore = globalThis.__SUDA_PUCK_CORE__;
|
|
595
|
-
if (!PuckCore) {
|
|
596
|
-
throw new Error("Missing host Puck core runtime (__SUDA_PUCK_CORE__).");
|
|
597
|
-
}
|
|
598
|
-
export default PuckCore;
|
|
599
|
-
export const {
|
|
600
|
-
Action,
|
|
601
|
-
ActionBar,
|
|
602
|
-
AutoField,
|
|
603
|
-
Button,
|
|
604
|
-
Drawer,
|
|
605
|
-
DropZone,
|
|
606
|
-
FieldLabel,
|
|
607
|
-
Group,
|
|
608
|
-
IconButton,
|
|
609
|
-
Label,
|
|
610
|
-
Puck,
|
|
611
|
-
Render,
|
|
612
|
-
RichTextMenu,
|
|
613
|
-
Separator,
|
|
614
|
-
blocksPlugin,
|
|
615
|
-
createUsePuck,
|
|
616
|
-
fieldsPlugin,
|
|
617
|
-
legacySideBarPlugin,
|
|
618
|
-
migrate,
|
|
619
|
-
outlinePlugin,
|
|
620
|
-
overrideKeys,
|
|
621
|
-
registerOverlayPortal,
|
|
622
|
-
renderContext,
|
|
623
|
-
resolveAllData,
|
|
624
|
-
setDeep,
|
|
625
|
-
transformProps,
|
|
626
|
-
useGetPuck,
|
|
627
|
-
usePuck,
|
|
628
|
-
walkTree,
|
|
629
|
-
} = PuckCore;
|
|
630
|
-
`
|
|
587
|
+
build.onResolve({ filter: /^@puckeditor\/(?:core|plugin-ai)(?:\/.*)?$/ }, (args) => ({
|
|
588
|
+
errors: [
|
|
589
|
+
{
|
|
590
|
+
text: `${args.path} is an editor dependency and cannot be imported by the public theme client runtime.`
|
|
591
|
+
}
|
|
592
|
+
]
|
|
631
593
|
}));
|
|
632
594
|
}
|
|
633
595
|
};
|
|
634
596
|
}
|
|
635
|
-
function
|
|
597
|
+
function createHostThemeRuntimeShimPlugin() {
|
|
636
598
|
return {
|
|
637
|
-
name: "suda-theme-
|
|
599
|
+
name: "suda-host-theme-runtime-shim",
|
|
638
600
|
setup(build) {
|
|
639
|
-
const themeRequire = createRequire(path2.join(root, "package.json"));
|
|
640
|
-
const runtimeEntry = themeRequire.resolve("@sudajs/theme-engine/runtime");
|
|
641
601
|
build.onResolve({ filter: /^@sudajs\/theme-engine\/runtime$/ }, () => ({
|
|
642
|
-
path:
|
|
602
|
+
path: "theme-runtime-host-shim",
|
|
603
|
+
namespace: "suda-shim"
|
|
604
|
+
}));
|
|
605
|
+
build.onLoad({ filter: /^theme-runtime-host-shim$/, namespace: "suda-shim" }, () => ({
|
|
606
|
+
loader: "js",
|
|
607
|
+
contents: `
|
|
608
|
+
const ThemeRuntime = globalThis.__SUDA_THEME_RUNTIME__;
|
|
609
|
+
if (!ThemeRuntime) {
|
|
610
|
+
throw new Error("Missing host theme runtime (__SUDA_THEME_RUNTIME__).");
|
|
611
|
+
}
|
|
612
|
+
export const {
|
|
613
|
+
SudaLucideIcon,
|
|
614
|
+
isSudaLucideIconName,
|
|
615
|
+
normalizeSudaLucideIconName,
|
|
616
|
+
colorSchemeField,
|
|
617
|
+
createColorSchemeDefault,
|
|
618
|
+
createThemeCssVariables,
|
|
619
|
+
designSystemField,
|
|
620
|
+
resolveColorSchemeTokens,
|
|
621
|
+
SUDA_COLOR_TOKEN_KEYS,
|
|
622
|
+
createThemeDesignCssVariables,
|
|
623
|
+
createThemeDesignDefault,
|
|
624
|
+
resolveThemeDesignTokens,
|
|
625
|
+
SUDA_CUSTOM_DESIGN_PRESET_ID,
|
|
626
|
+
SUDA_DESIGN_COLOR_KEYS,
|
|
627
|
+
SUDA_DESIGN_RADIUS_KEYS,
|
|
628
|
+
compileSudaBlockSlots,
|
|
629
|
+
isSudaLocalBlockComponent,
|
|
630
|
+
SUDA_LOCAL_BLOCK_CATEGORY,
|
|
631
|
+
SUDA_LOCAL_BLOCK_TYPE_PREFIX,
|
|
632
|
+
cmsPaginationUrl,
|
|
633
|
+
getIcpRecord,
|
|
634
|
+
getCmsContent,
|
|
635
|
+
getContactForm,
|
|
636
|
+
getPostResource,
|
|
637
|
+
getWhiteLabel,
|
|
638
|
+
resolveAsset,
|
|
639
|
+
postResourceKey,
|
|
640
|
+
getPageSlot,
|
|
641
|
+
THEME_PAGE_SLOT,
|
|
642
|
+
createThemeAsset,
|
|
643
|
+
createThemeAssetPath,
|
|
644
|
+
createThemeAssetResolver,
|
|
645
|
+
resolveThemeClientRuntimeUrl,
|
|
646
|
+
resolveThemeRuntimeUrl,
|
|
647
|
+
resolveAssetPath,
|
|
648
|
+
THEME_CLIENT_RUNTIME_ARTIFACT,
|
|
649
|
+
clearThemeLocale,
|
|
650
|
+
createThemeTranslate,
|
|
651
|
+
installThemeLocale,
|
|
652
|
+
clearThemePreviewLocale,
|
|
653
|
+
createThemePreviewTranslate,
|
|
654
|
+
installThemePreviewLocale,
|
|
655
|
+
} = ThemeRuntime;
|
|
656
|
+
`
|
|
643
657
|
}));
|
|
644
658
|
}
|
|
645
659
|
};
|
|
646
660
|
}
|
|
647
|
-
function
|
|
661
|
+
function createHostThemeIconsShimPlugin() {
|
|
648
662
|
return {
|
|
649
|
-
name: "suda-theme-
|
|
663
|
+
name: "suda-host-theme-icons-shim",
|
|
650
664
|
setup(build) {
|
|
651
|
-
const themeRequire = createRequire(path2.join(root, "package.json"));
|
|
652
|
-
const themeEngineEntry = themeRequire.resolve("@sudajs/theme-engine");
|
|
653
|
-
const entryDir = path2.dirname(themeEngineEntry);
|
|
654
|
-
const packageRoot2 = path2.basename(entryDir) === "dist" || path2.basename(entryDir) === "src" ? path2.dirname(entryDir) : entryDir;
|
|
655
|
-
const sourceEntry = path2.join(packageRoot2, "src", "icons", "lucide-icon.hydrate.client.tsx");
|
|
656
665
|
build.onResolve({ filter: /^@sudajs\/theme-engine\/icons$/ }, () => {
|
|
657
|
-
|
|
658
|
-
return { path: sourceEntry };
|
|
659
|
-
}
|
|
660
|
-
return { path: themeRequire.resolve("@sudajs/theme-engine/icons") };
|
|
666
|
+
return { path: "theme-icons-host-shim", namespace: "suda-shim" };
|
|
661
667
|
});
|
|
668
|
+
build.onLoad({ filter: /^theme-icons-host-shim$/, namespace: "suda-shim" }, () => ({
|
|
669
|
+
loader: "js",
|
|
670
|
+
contents: `
|
|
671
|
+
const ThemeIcons = globalThis.__SUDA_THEME_ICONS__;
|
|
672
|
+
if (!ThemeIcons) {
|
|
673
|
+
throw new Error("Missing host theme icon runtime (__SUDA_THEME_ICONS__).");
|
|
674
|
+
}
|
|
675
|
+
export const {
|
|
676
|
+
SudaLucideIcon,
|
|
677
|
+
isSudaLucideIconName,
|
|
678
|
+
normalizeSudaLucideIconName,
|
|
679
|
+
} = ThemeIcons;
|
|
680
|
+
`
|
|
681
|
+
}));
|
|
662
682
|
}
|
|
663
683
|
};
|
|
664
684
|
}
|
|
@@ -779,9 +799,9 @@ ${issues}`);
|
|
|
779
799
|
if (module.manifest.entry !== "index.js") {
|
|
780
800
|
throw new Error('manifest.entry must be artifact-local: "index.js".');
|
|
781
801
|
}
|
|
782
|
-
if (
|
|
802
|
+
if ("clientEntry" in module.manifest) {
|
|
783
803
|
throw new Error(
|
|
784
|
-
|
|
804
|
+
"manifest.clientEntry is no longer supported. The CLI owns the runtime.client.js artifact."
|
|
785
805
|
);
|
|
786
806
|
}
|
|
787
807
|
assertRecord(module.pageConfig?.components, "pageConfig.components");
|
|
@@ -839,6 +859,7 @@ async function validateTheme(root) {
|
|
|
839
859
|
validateThemeModule(module);
|
|
840
860
|
await validateThemeTailwindContract(root);
|
|
841
861
|
await validateThemeLocales(root);
|
|
862
|
+
await validateThemePreviewLocales(root, module.manifest.previewLocales);
|
|
842
863
|
const result = {
|
|
843
864
|
root,
|
|
844
865
|
module,
|
|
@@ -885,7 +906,16 @@ async function listLocaleFiles(localesDir) {
|
|
|
885
906
|
return [];
|
|
886
907
|
}
|
|
887
908
|
const entries = await readdir(localesDir, { withFileTypes: true });
|
|
888
|
-
return entries.filter(
|
|
909
|
+
return entries.filter(
|
|
910
|
+
(entry) => entry.isFile() && entry.name.endsWith(".json") && !entry.name.endsWith(".content.json")
|
|
911
|
+
).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
912
|
+
}
|
|
913
|
+
async function listPreviewLocaleFiles(localesDir) {
|
|
914
|
+
if (!await pathExists(localesDir)) {
|
|
915
|
+
return [];
|
|
916
|
+
}
|
|
917
|
+
const entries = await readdir(localesDir, { withFileTypes: true });
|
|
918
|
+
return entries.filter((entry) => entry.isFile() && entry.name.endsWith(".content.json")).map((entry) => entry.name).sort((a, b) => a.localeCompare(b));
|
|
889
919
|
}
|
|
890
920
|
async function validateThemeLocales(root) {
|
|
891
921
|
const localesDir = path2.join(root, "dist", "locales");
|
|
@@ -921,6 +951,47 @@ ${details}`
|
|
|
921
951
|
}
|
|
922
952
|
}
|
|
923
953
|
}
|
|
954
|
+
async function validateThemePreviewLocales(root, previewLocales) {
|
|
955
|
+
const localesDir = path2.join(root, "dist", "locales");
|
|
956
|
+
const files = await listPreviewLocaleFiles(localesDir);
|
|
957
|
+
const declared = previewLocales ?? [];
|
|
958
|
+
const expectedFiles = new Set(declared.map((locale) => `${locale}.content.json`));
|
|
959
|
+
const undeclared = files.filter((file) => !expectedFiles.has(file));
|
|
960
|
+
const missing = [...expectedFiles].filter((file) => !files.includes(file));
|
|
961
|
+
if (missing.length > 0 || undeclared.length > 0) {
|
|
962
|
+
const details = [
|
|
963
|
+
...missing.map((file) => ` - missing locales/${file}`),
|
|
964
|
+
...undeclared.map((file) => ` - undeclared locales/${file}`)
|
|
965
|
+
].join("\n");
|
|
966
|
+
throw new Error(`Theme preview locale files do not match manifest.previewLocales:
|
|
967
|
+
${details}`);
|
|
968
|
+
}
|
|
969
|
+
if (declared.length === 0) {
|
|
970
|
+
return;
|
|
971
|
+
}
|
|
972
|
+
const referenceFile = "en.content.json";
|
|
973
|
+
const referenceMessages = await readLocaleJson(path2.join(localesDir, referenceFile));
|
|
974
|
+
const referenceKeys = new Set(collectLocaleKeys(referenceMessages).sort());
|
|
975
|
+
for (const file of files) {
|
|
976
|
+
if (file === referenceFile) {
|
|
977
|
+
continue;
|
|
978
|
+
}
|
|
979
|
+
const messages = await readLocaleJson(path2.join(localesDir, file));
|
|
980
|
+
const keys = new Set(collectLocaleKeys(messages).sort());
|
|
981
|
+
const missingKeys = [...referenceKeys].filter((key) => !keys.has(key));
|
|
982
|
+
const extraKeys = [...keys].filter((key) => !referenceKeys.has(key));
|
|
983
|
+
if (missingKeys.length > 0 || extraKeys.length > 0) {
|
|
984
|
+
const details = [
|
|
985
|
+
...missingKeys.map((key) => ` - missing ${key}`),
|
|
986
|
+
...extraKeys.map((key) => ` - extra ${key}`)
|
|
987
|
+
].join("\n");
|
|
988
|
+
throw new Error(
|
|
989
|
+
`Theme preview locale ${file} does not match ${referenceFile} key set:
|
|
990
|
+
${details}`
|
|
991
|
+
);
|
|
992
|
+
}
|
|
993
|
+
}
|
|
994
|
+
}
|
|
924
995
|
var VITE_CONFIG_FILES = [
|
|
925
996
|
"vite.config.ts",
|
|
926
997
|
"vite.config.mts",
|
|
@@ -950,18 +1021,6 @@ async function loadThemeVite(root) {
|
|
|
950
1021
|
}
|
|
951
1022
|
return await import(pathToFileURL(viteEntry).href);
|
|
952
1023
|
}
|
|
953
|
-
async function findClientEntry(root) {
|
|
954
|
-
const candidates = [
|
|
955
|
-
path2.join(root, "src", "runtime.client.tsx"),
|
|
956
|
-
path2.join(root, "src", "runtime.client.ts")
|
|
957
|
-
];
|
|
958
|
-
for (const candidate of candidates) {
|
|
959
|
-
if (await pathExists(candidate)) {
|
|
960
|
-
return candidate;
|
|
961
|
-
}
|
|
962
|
-
}
|
|
963
|
-
throw new Error(`Missing runtime entry. Add src/runtime.client.ts(x) in ${root}.`);
|
|
964
|
-
}
|
|
965
1024
|
async function findServerEntry(root) {
|
|
966
1025
|
const candidates = [path2.join(root, "src", "index.tsx"), path2.join(root, "src", "index.ts")];
|
|
967
1026
|
for (const candidate of candidates) {
|
|
@@ -1062,38 +1121,74 @@ async function copyThemeLocales(root) {
|
|
|
1062
1121
|
await copyDirectory(sourceLocales, path2.join(root, "dist", "locales"));
|
|
1063
1122
|
}
|
|
1064
1123
|
async function buildClientRuntime(root, minify) {
|
|
1065
|
-
const
|
|
1066
|
-
|
|
1067
|
-
|
|
1124
|
+
const legacyEntries = [
|
|
1125
|
+
path2.join(root, "src", "runtime.client.ts"),
|
|
1126
|
+
path2.join(root, "src", "runtime.client.tsx")
|
|
1127
|
+
];
|
|
1128
|
+
if (legacyEntries.some((entry) => existsSync(entry))) {
|
|
1129
|
+
throw new Error(
|
|
1130
|
+
"Theme-authored src/runtime.client.ts(x) is no longer supported. Move optional browser setup to src/client.ts as clientHooks; the CLI owns runtime.client.js."
|
|
1131
|
+
);
|
|
1068
1132
|
}
|
|
1069
1133
|
const packageJson = JSON.parse(await readFile(path2.join(root, "package.json"), "utf8"));
|
|
1070
1134
|
const themeVersion = typeof packageJson.version === "string" ? packageJson.version : "0.0.0";
|
|
1135
|
+
const clientHooksPath = path2.join(root, "src", "client.ts");
|
|
1136
|
+
const generatedEntry = path2.join(root, ".suda-build", "runtime.client.ts");
|
|
1137
|
+
const clientHooksImport = await pathExists(clientHooksPath) ? 'import { clientHooks } from "../src/client.js";' : "const clientHooks = {};";
|
|
1138
|
+
await mkdir(path2.dirname(generatedEntry), { recursive: true });
|
|
1139
|
+
await writeFile(
|
|
1140
|
+
generatedEntry,
|
|
1141
|
+
[
|
|
1142
|
+
'"use client";',
|
|
1143
|
+
'import type { ThemeClientHooks, ThemeClientRuntime, ThemeRenderModule } from "@sudajs/theme-engine/runtime";',
|
|
1144
|
+
'import * as themeConfig from "../src/config.js";',
|
|
1145
|
+
clientHooksImport,
|
|
1146
|
+
"const theme: ThemeRenderModule = {",
|
|
1147
|
+
" pageConfig: themeConfig.pageConfig,",
|
|
1148
|
+
" layoutConfig: themeConfig.layoutConfig,",
|
|
1149
|
+
"};",
|
|
1150
|
+
'if ("rendering" in themeConfig && themeConfig.rendering !== undefined) {',
|
|
1151
|
+
" theme.rendering = themeConfig.rendering;",
|
|
1152
|
+
"}",
|
|
1153
|
+
"const hooks: ThemeClientHooks = clientHooks;",
|
|
1154
|
+
"const runtime: ThemeClientRuntime = { theme };",
|
|
1155
|
+
"if (hooks.initialize !== undefined) {",
|
|
1156
|
+
" runtime.initialize = hooks.initialize;",
|
|
1157
|
+
"}",
|
|
1158
|
+
"export default runtime;",
|
|
1159
|
+
""
|
|
1160
|
+
].join("\n"),
|
|
1161
|
+
"utf8"
|
|
1162
|
+
);
|
|
1071
1163
|
await mkdir(path2.join(root, "dist"), { recursive: true });
|
|
1072
1164
|
const hostReactShimPlugin = createHostReactShimPlugin();
|
|
1073
|
-
const
|
|
1074
|
-
const
|
|
1075
|
-
const
|
|
1165
|
+
const rejectEditorRuntimeDependencyPlugin = createRejectEditorRuntimeDependencyPlugin();
|
|
1166
|
+
const hostThemeRuntimeShimPlugin = createHostThemeRuntimeShimPlugin();
|
|
1167
|
+
const hostThemeIconsShimPlugin = createHostThemeIconsShimPlugin();
|
|
1076
1168
|
const themeDependencyResolverPlugin = createThemeDependencyResolverPlugin(root);
|
|
1077
1169
|
await build({
|
|
1078
1170
|
bundle: true,
|
|
1079
|
-
entryPoints:
|
|
1171
|
+
entryPoints: { "runtime.client": generatedEntry },
|
|
1080
1172
|
external: ["react", "react/*", "react-dom", "react-dom/*"],
|
|
1081
1173
|
format: "esm",
|
|
1082
1174
|
jsx: "automatic",
|
|
1175
|
+
logOverride: { "import-is-undefined": "silent" },
|
|
1083
1176
|
minify,
|
|
1084
|
-
|
|
1177
|
+
outdir: path2.join(root, "dist"),
|
|
1085
1178
|
platform: "browser",
|
|
1086
1179
|
define: {
|
|
1087
1180
|
__SUDA_THEME_VERSION__: JSON.stringify(themeVersion)
|
|
1088
1181
|
},
|
|
1089
1182
|
plugins: [
|
|
1090
1183
|
hostReactShimPlugin,
|
|
1091
|
-
|
|
1092
|
-
|
|
1093
|
-
|
|
1184
|
+
rejectEditorRuntimeDependencyPlugin,
|
|
1185
|
+
hostThemeRuntimeShimPlugin,
|
|
1186
|
+
hostThemeIconsShimPlugin,
|
|
1094
1187
|
themeDependencyResolverPlugin
|
|
1095
1188
|
],
|
|
1096
1189
|
sourcemap: false,
|
|
1190
|
+
splitting: true,
|
|
1191
|
+
chunkNames: "chunks/[name]-[hash]",
|
|
1097
1192
|
target: "es2022",
|
|
1098
1193
|
treeShaking: true
|
|
1099
1194
|
});
|
|
@@ -1284,6 +1379,9 @@ var __testUtils = {
|
|
|
1284
1379
|
validateThemeName,
|
|
1285
1380
|
validateThemeTailwindContract,
|
|
1286
1381
|
validateThemeLocales,
|
|
1382
|
+
validateThemePreviewLocales,
|
|
1383
|
+
resolveThemePreviewLocale,
|
|
1384
|
+
withSerializedVitePreviewLocaleLoad,
|
|
1287
1385
|
toViteModuleUrl,
|
|
1288
1386
|
updateRemotePageDraft
|
|
1289
1387
|
};
|
|
@@ -1381,7 +1479,15 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1381
1479
|
return;
|
|
1382
1480
|
}
|
|
1383
1481
|
try {
|
|
1384
|
-
const
|
|
1482
|
+
const acceptLanguageHeader = request.headers["accept-language"];
|
|
1483
|
+
const acceptLanguage = Array.isArray(acceptLanguageHeader) ? acceptLanguageHeader.join(",") : acceptLanguageHeader;
|
|
1484
|
+
const explicitLang = url.searchParams.get("lang");
|
|
1485
|
+
const { theme, locale } = await loadLocalizedViteDevTheme(
|
|
1486
|
+
root,
|
|
1487
|
+
server,
|
|
1488
|
+
explicitLang,
|
|
1489
|
+
acceptLanguage
|
|
1490
|
+
);
|
|
1385
1491
|
const renderer = await loadThemeScopedRenderer(root);
|
|
1386
1492
|
if (slug === null) {
|
|
1387
1493
|
const homeSlug = pickStarterSlug(theme);
|
|
@@ -1398,7 +1504,13 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1398
1504
|
}
|
|
1399
1505
|
const html2 = await server.transformIndexHtml(
|
|
1400
1506
|
url.pathname,
|
|
1401
|
-
renderDevStarterPageHtml(
|
|
1507
|
+
renderDevStarterPageHtml(
|
|
1508
|
+
theme,
|
|
1509
|
+
starter2,
|
|
1510
|
+
renderer,
|
|
1511
|
+
locale,
|
|
1512
|
+
canonicalLocale(explicitLang) !== null
|
|
1513
|
+
)
|
|
1402
1514
|
);
|
|
1403
1515
|
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1404
1516
|
response.end(html2);
|
|
@@ -1412,7 +1524,13 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1412
1524
|
}
|
|
1413
1525
|
const html = await server.transformIndexHtml(
|
|
1414
1526
|
url.pathname,
|
|
1415
|
-
renderDevStarterPageHtml(
|
|
1527
|
+
renderDevStarterPageHtml(
|
|
1528
|
+
theme,
|
|
1529
|
+
starter,
|
|
1530
|
+
renderer,
|
|
1531
|
+
locale,
|
|
1532
|
+
canonicalLocale(explicitLang) !== null
|
|
1533
|
+
)
|
|
1416
1534
|
);
|
|
1417
1535
|
response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
|
|
1418
1536
|
response.end(html);
|
|
@@ -1425,6 +1543,48 @@ function createSudaPreviewVitePlugin(root) {
|
|
|
1425
1543
|
}
|
|
1426
1544
|
};
|
|
1427
1545
|
}
|
|
1546
|
+
async function loadLocalizedViteDevTheme(root, server, explicitLang, acceptLanguage) {
|
|
1547
|
+
return withSerializedVitePreviewLocaleLoad(server, async () => {
|
|
1548
|
+
const baseTheme = await loadViteDevTheme(root, server);
|
|
1549
|
+
const localeParams = {
|
|
1550
|
+
previewLocales: baseTheme.module.manifest.previewLocales,
|
|
1551
|
+
explicitLang
|
|
1552
|
+
};
|
|
1553
|
+
if (acceptLanguage !== void 0) {
|
|
1554
|
+
localeParams.acceptLanguage = acceptLanguage;
|
|
1555
|
+
}
|
|
1556
|
+
const locale = resolveThemePreviewLocale(localeParams);
|
|
1557
|
+
if (!baseTheme.module.manifest.previewLocales) {
|
|
1558
|
+
return { theme: baseTheme, locale };
|
|
1559
|
+
}
|
|
1560
|
+
const messages = await readDevPreviewLocaleMessages(root, locale);
|
|
1561
|
+
server.moduleGraph.invalidateAll();
|
|
1562
|
+
const theme = await runWithThemePreviewLocale(
|
|
1563
|
+
messages,
|
|
1564
|
+
() => loadViteDevTheme(root, server)
|
|
1565
|
+
);
|
|
1566
|
+
return { theme, locale };
|
|
1567
|
+
});
|
|
1568
|
+
}
|
|
1569
|
+
var vitePreviewLocaleLoadTails = /* @__PURE__ */ new WeakMap();
|
|
1570
|
+
async function withSerializedVitePreviewLocaleLoad(server, load) {
|
|
1571
|
+
const previous = vitePreviewLocaleLoadTails.get(server) ?? Promise.resolve();
|
|
1572
|
+
let release;
|
|
1573
|
+
const gate = new Promise((resolve) => {
|
|
1574
|
+
release = resolve;
|
|
1575
|
+
});
|
|
1576
|
+
const tail = previous.then(() => gate);
|
|
1577
|
+
vitePreviewLocaleLoadTails.set(server, tail);
|
|
1578
|
+
await previous;
|
|
1579
|
+
try {
|
|
1580
|
+
return await load();
|
|
1581
|
+
} finally {
|
|
1582
|
+
release();
|
|
1583
|
+
if (vitePreviewLocaleLoadTails.get(server) === tail) {
|
|
1584
|
+
vitePreviewLocaleLoadTails.delete(server);
|
|
1585
|
+
}
|
|
1586
|
+
}
|
|
1587
|
+
}
|
|
1428
1588
|
async function loadViteDevTheme(root, server) {
|
|
1429
1589
|
const serverEntryPath = await findServerEntry(root);
|
|
1430
1590
|
const imported = await server.ssrLoadModule(toViteModuleUrl(root, serverEntryPath));
|
|
@@ -1461,6 +1621,73 @@ function resolveDevStarterSlug(pathname) {
|
|
|
1461
1621
|
}
|
|
1462
1622
|
return decodeURIComponent(normalized);
|
|
1463
1623
|
}
|
|
1624
|
+
function canonicalLocale(value) {
|
|
1625
|
+
const candidate = value?.trim();
|
|
1626
|
+
if (!candidate || candidate === "*") {
|
|
1627
|
+
return null;
|
|
1628
|
+
}
|
|
1629
|
+
try {
|
|
1630
|
+
return Intl.getCanonicalLocales(candidate)[0] ?? null;
|
|
1631
|
+
} catch {
|
|
1632
|
+
return null;
|
|
1633
|
+
}
|
|
1634
|
+
}
|
|
1635
|
+
function acceptLanguageLocales(value) {
|
|
1636
|
+
if (!value) {
|
|
1637
|
+
return [];
|
|
1638
|
+
}
|
|
1639
|
+
return value.split(",").map((part, index) => {
|
|
1640
|
+
const [rawLocale, ...parameters] = part.trim().split(";");
|
|
1641
|
+
const locale = canonicalLocale(rawLocale);
|
|
1642
|
+
const qualityParameter = parameters.find((parameter) => parameter.trim().startsWith("q="));
|
|
1643
|
+
const parsedQuality = qualityParameter ? Number.parseFloat(qualityParameter.trim().slice(2)) : 1;
|
|
1644
|
+
const quality = Number.isFinite(parsedQuality) ? parsedQuality : 0;
|
|
1645
|
+
return { locale, quality, index };
|
|
1646
|
+
}).filter(
|
|
1647
|
+
(entry) => Boolean(entry.locale) && entry.quality > 0
|
|
1648
|
+
).sort((left, right) => right.quality - left.quality || left.index - right.index).map((entry) => entry.locale);
|
|
1649
|
+
}
|
|
1650
|
+
function localeLookupCandidates(locale) {
|
|
1651
|
+
const candidates = [];
|
|
1652
|
+
let current = locale;
|
|
1653
|
+
while (current) {
|
|
1654
|
+
candidates.push(current);
|
|
1655
|
+
const separator = current.lastIndexOf("-");
|
|
1656
|
+
if (separator < 0) {
|
|
1657
|
+
break;
|
|
1658
|
+
}
|
|
1659
|
+
current = current.slice(0, separator);
|
|
1660
|
+
}
|
|
1661
|
+
return candidates;
|
|
1662
|
+
}
|
|
1663
|
+
function resolveThemePreviewLocale(params) {
|
|
1664
|
+
const supported = params.previewLocales ?? ["en"];
|
|
1665
|
+
const supportedByNormalized = new Map(
|
|
1666
|
+
supported.map((locale) => [locale.toLowerCase(), locale])
|
|
1667
|
+
);
|
|
1668
|
+
const explicitLocale = canonicalLocale(params.explicitLang);
|
|
1669
|
+
const requestedLocales = explicitLocale ? [explicitLocale] : acceptLanguageLocales(params.acceptLanguage);
|
|
1670
|
+
for (const requested of requestedLocales) {
|
|
1671
|
+
for (const candidate of localeLookupCandidates(requested)) {
|
|
1672
|
+
const supportedLocale = supportedByNormalized.get(candidate.toLowerCase());
|
|
1673
|
+
if (supportedLocale) {
|
|
1674
|
+
return supportedLocale;
|
|
1675
|
+
}
|
|
1676
|
+
}
|
|
1677
|
+
if (explicitLocale) {
|
|
1678
|
+
break;
|
|
1679
|
+
}
|
|
1680
|
+
}
|
|
1681
|
+
return supportedByNormalized.get("en") ?? "en";
|
|
1682
|
+
}
|
|
1683
|
+
async function readDevPreviewLocaleMessages(root, locale) {
|
|
1684
|
+
const filePath = path2.join(root, "src", "locales", `${locale}.content.json`);
|
|
1685
|
+
const messages = await readLocaleJson(filePath);
|
|
1686
|
+
if (!isRecord(messages)) {
|
|
1687
|
+
throw new Error(`Theme preview locale ${locale} must be a JSON object.`);
|
|
1688
|
+
}
|
|
1689
|
+
return messages;
|
|
1690
|
+
}
|
|
1464
1691
|
function escapeHtml(value) {
|
|
1465
1692
|
return value.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">").replace(/"/g, """).replace(/'/g, "'");
|
|
1466
1693
|
}
|
|
@@ -1494,7 +1721,7 @@ async function loadThemeScopedRenderer(themeRoot) {
|
|
|
1494
1721
|
renderToString: reactDomServerModule.renderToString
|
|
1495
1722
|
};
|
|
1496
1723
|
}
|
|
1497
|
-
function renderDevStarterPageHtml(theme, page, renderer) {
|
|
1724
|
+
function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserveLang = false) {
|
|
1498
1725
|
const chrome = renderer.extractLayoutChrome(theme.module.defaultLayout);
|
|
1499
1726
|
const metadata = {
|
|
1500
1727
|
resolveAssetUrl: createPreviewAssetResolver(renderer.resolveAssetPath),
|
|
@@ -1513,7 +1740,7 @@ function renderDevStarterPageHtml(theme, page, renderer) {
|
|
|
1513
1740
|
const customBody = chrome.customBodyCode ?? "";
|
|
1514
1741
|
return [
|
|
1515
1742
|
"<!doctype html>",
|
|
1516
|
-
|
|
1743
|
+
`<html lang="${escapeHtml(locale)}">`,
|
|
1517
1744
|
"<head>",
|
|
1518
1745
|
'<meta charset="utf-8" />',
|
|
1519
1746
|
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
@@ -1524,6 +1751,9 @@ function renderDevStarterPageHtml(theme, page, renderer) {
|
|
|
1524
1751
|
`<body${cssVarStyle ? ` style="${cssVarStyle}"` : ""}>`,
|
|
1525
1752
|
body,
|
|
1526
1753
|
customBody,
|
|
1754
|
+
...preserveLang ? [
|
|
1755
|
+
'<script>for(const a of document.querySelectorAll("a[href]")){try{const u=new URL(a.href,location.href);if(u.origin===location.origin){u.searchParams.set("lang",new URL(location.href).searchParams.get("lang"));a.href=u.pathname+u.search+u.hash}}catch{}}</script>'
|
|
1756
|
+
] : [],
|
|
1527
1757
|
"</body>",
|
|
1528
1758
|
"</html>"
|
|
1529
1759
|
].join("");
|