@sudajs/cli 0.18.8 → 0.18.10

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  #!/usr/bin/env node
2
2
  import { ThemeModule, ThemeStarterPage } from '@sudajs/theme-engine';
3
- import { ThemeRender, extractLayoutChrome, resolveAssetPath } from '@sudajs/theme-engine/server';
3
+ import { ThemeRender, extractLayoutChrome, resolveAssetPath, ThemePreviewLocaleMessages } from '@sudajs/theme-engine/server';
4
4
  import { Command } from 'commander';
5
5
  import { createElement } from 'react';
6
6
  import { renderToString } from 'react-dom/server';
@@ -1816,6 +1816,7 @@ declare const __testUtils: {
1816
1816
  performUpdateThemeSettings: typeof performUpdateThemeSettings;
1817
1817
  getRemoteThemeSettings: typeof getRemoteThemeSettings;
1818
1818
  renderDevStarterPageHtml: typeof renderDevStarterPageHtml;
1819
+ writeVitePreviewEntries: typeof writeVitePreviewEntries;
1819
1820
  resolveDevStarterSlug: typeof resolveDevStarterSlug;
1820
1821
  resolveScreenshotOptions: typeof resolveScreenshotOptions;
1821
1822
  slugifyThemeName: typeof slugifyThemeName;
@@ -1831,6 +1832,7 @@ declare const __testUtils: {
1831
1832
  updateRemotePageDraft: typeof updateRemotePageDraft;
1832
1833
  };
1833
1834
  declare function watchTheme(root: string, port: number): Promise<void>;
1835
+ declare function writeVitePreviewEntries(root: string): Promise<void>;
1834
1836
  declare function withSerializedVitePreviewLocaleLoad<Result>(server: object, load: () => Promise<Result>): Promise<Result>;
1835
1837
  declare function resolveDevStarterSlug(pathname: string): string | null | undefined;
1836
1838
  declare function resolveThemePreviewLocale(params: {
@@ -1856,7 +1858,7 @@ interface ThemeRenderer {
1856
1858
  renderToString: typeof renderToString;
1857
1859
  }
1858
1860
  declare function loadThemeScopedRenderer(themeRoot: string): Promise<ThemeRenderer>;
1859
- declare function renderDevStarterPageHtml(theme: ValidatedTheme, page: ThemeStarterPage, renderer: ThemeRenderer, locale?: string, preserveLang?: boolean): string;
1861
+ declare function renderDevStarterPageHtml(theme: ValidatedTheme, page: ThemeStarterPage, renderer: ThemeRenderer, locale?: string, preserveLang?: boolean, previewLocaleMessages?: ThemePreviewLocaleMessages): string;
1860
1862
  interface ScreenshotOptions extends ThemeRootOptions {
1861
1863
  device?: string[] | string;
1862
1864
  output?: string;
package/dist/index.js CHANGED
@@ -1,7 +1,7 @@
1
1
  #!/usr/bin/env node
2
2
  import { createHash } from 'crypto';
3
3
  import { existsSync } from 'fs';
4
- import fs, { readFile, writeFile, stat, readdir, mkdir, copyFile, rm } from 'fs/promises';
4
+ import fs, { readFile, mkdir, writeFile, stat, readdir, copyFile, rm } from 'fs/promises';
5
5
  import { createRequire } from 'module';
6
6
  import path2 from 'path';
7
7
  import { createInterface } from 'readline/promises';
@@ -12,6 +12,9 @@ import { themeDesignSystemSchema, createThemeDesignCssVariables, createThemeAgen
12
12
  import { themeManifestSchema, runWithThemePreviewLocale } from '@sudajs/theme-engine/server';
13
13
  import { Command } from 'commander';
14
14
  import { build } from 'esbuild';
15
+ import { CircleCheck } from 'lucide-react';
16
+ import { createElement } from 'react';
17
+ import { renderToStaticMarkup } from 'react-dom/server';
15
18
  import { z } from 'zod';
16
19
  import os from 'os';
17
20
  import pc from 'picocolors';
@@ -911,6 +914,7 @@ export const {
911
914
  SUDA_LOCAL_BLOCK_TYPE_PREFIX,
912
915
  cmsPaginationUrl,
913
916
  getIcpRecord,
917
+ getPublicSecurityRecord,
914
918
  getCmsContent,
915
919
  getContactForm,
916
920
  getPostResource,
@@ -1519,26 +1523,7 @@ async function buildClientRuntime(root, minify) {
1519
1523
  await mkdir(path2.dirname(generatedEntry), { recursive: true });
1520
1524
  await writeFile(
1521
1525
  generatedEntry,
1522
- [
1523
- '"use client";',
1524
- 'import type { ThemeClientHooks, ThemeClientRuntime, ThemeRenderModule } from "@sudajs/theme-engine/runtime";',
1525
- 'import * as themeConfig from "../src/config.js";',
1526
- clientHooksImport,
1527
- "const theme: ThemeRenderModule = {",
1528
- " pageConfig: themeConfig.pageConfig,",
1529
- " layoutConfig: themeConfig.layoutConfig,",
1530
- "};",
1531
- 'if ("rendering" in themeConfig && themeConfig.rendering !== undefined) {',
1532
- " theme.rendering = themeConfig.rendering;",
1533
- "}",
1534
- "const hooks: ThemeClientHooks = clientHooks;",
1535
- "const runtime: ThemeClientRuntime = { theme };",
1536
- "if (hooks.initialize !== undefined) {",
1537
- " runtime.initialize = hooks.initialize;",
1538
- "}",
1539
- "export default runtime;",
1540
- ""
1541
- ].join("\n"),
1526
+ createClientRuntimeSource(clientHooksImport, true),
1542
1527
  "utf8"
1543
1528
  );
1544
1529
  await mkdir(path2.join(root, "dist"), { recursive: true });
@@ -1753,6 +1738,7 @@ var __testUtils = {
1753
1738
  performUpdateThemeSettings,
1754
1739
  getRemoteThemeSettings,
1755
1740
  renderDevStarterPageHtml,
1741
+ writeVitePreviewEntries,
1756
1742
  resolveDevStarterSlug,
1757
1743
  resolveScreenshotOptions,
1758
1744
  slugifyThemeName,
@@ -1824,13 +1810,17 @@ async function watchTheme(root, port) {
1824
1810
  async function startViteDevPreviewServer(root, port) {
1825
1811
  const viteConfig = await findViteConfig(root);
1826
1812
  const vite = await loadThemeVite(root);
1813
+ await writeVitePreviewEntries(root);
1827
1814
  const host = "127.0.0.1";
1828
1815
  const server = await vite.createServer({
1829
1816
  root,
1830
1817
  configFile: viteConfig,
1831
1818
  configLoader: "runner",
1832
- cacheDir: path2.join(root, ".suda-build", "vite-cache"),
1819
+ cacheDir: path2.join(root, ".suda-build", "node_modules", ".vite"),
1833
1820
  appType: "custom",
1821
+ optimizeDeps: {
1822
+ entries: [".suda-build/preview-client.ts"]
1823
+ },
1834
1824
  server: { host, port },
1835
1825
  plugins: [createSudaPreviewVitePlugin(root)]
1836
1826
  });
@@ -1842,6 +1832,55 @@ async function startViteDevPreviewServer(root, port) {
1842
1832
  close: () => server.close()
1843
1833
  };
1844
1834
  }
1835
+ async function writeVitePreviewEntries(root) {
1836
+ const clientHooksPath = path2.join(root, "src", "client.ts");
1837
+ const clientHooksImport = existsSync(clientHooksPath) ? 'import { clientHooks } from "../src/client.js";' : "const clientHooks = {};";
1838
+ const buildDir = path2.join(root, ".suda-build");
1839
+ await mkdir(buildDir, { recursive: true });
1840
+ await writeFile(
1841
+ path2.join(buildDir, "runtime.dev.ts"),
1842
+ createClientRuntimeSource(clientHooksImport, false),
1843
+ "utf8"
1844
+ );
1845
+ await writeFile(
1846
+ path2.join(buildDir, "preview-client.ts"),
1847
+ [
1848
+ 'import { hydrateThemePage, loadThemeRuntime } from "@sudajs/theme-engine/client";',
1849
+ 'import { installThemePreviewLocale, resolveAssetPath } from "@sudajs/theme-engine/site-runtime";',
1850
+ 'const payloadElement=document.querySelector("#suda-theme-hydration-data");',
1851
+ 'if(!payloadElement){throw new Error("Missing Suda theme hydration payload.")}',
1852
+ "const payload=JSON.parse(payloadElement.textContent ?? '{}');",
1853
+ "if(payload.previewLocaleMessages){installThemePreviewLocale(payload.previewLocaleMessages)}",
1854
+ 'const runtime=(await loadThemeRuntime("/.suda-build/runtime.dev.ts")).default;',
1855
+ 'const metadata={contactForm:payload.contactForm,resolveAssetUrl:(value: string | undefined)=>resolveAssetPath(value,{legacyResolve:(key)=>key.startsWith("themes/")?`/api/themes/${key.slice("themes/".length)}`:undefined})};',
1856
+ 'await hydrateThemePage({container:document.querySelector("[data-theme-hydration-root]")!,runtime,pageData:payload.pageData,layoutData:payload.layoutData,metadata,onRecoverableError:(error)=>console.error("[theme-dev] theme hydration recovered from an error",error)});',
1857
+ ""
1858
+ ].join("\n"),
1859
+ "utf8"
1860
+ );
1861
+ }
1862
+ function createClientRuntimeSource(clientHooksImport, useClientDirective) {
1863
+ return [
1864
+ ...useClientDirective ? ['"use client";'] : [],
1865
+ 'import type { ThemeClientHooks, ThemeClientRuntime, ThemeRenderModule } from "@sudajs/theme-engine/runtime";',
1866
+ 'import * as themeConfig from "../src/config.js";',
1867
+ clientHooksImport,
1868
+ "const theme: ThemeRenderModule = {",
1869
+ " pageConfig: themeConfig.pageConfig,",
1870
+ " layoutConfig: themeConfig.layoutConfig,",
1871
+ "};",
1872
+ 'if ("rendering" in themeConfig && themeConfig.rendering !== undefined) {',
1873
+ " theme.rendering = themeConfig.rendering;",
1874
+ "}",
1875
+ "const hooks: ThemeClientHooks = clientHooks;",
1876
+ "const runtime: ThemeClientRuntime = { theme };",
1877
+ "if (hooks.initialize !== undefined) {",
1878
+ " runtime.initialize = hooks.initialize;",
1879
+ "}",
1880
+ "export default runtime;",
1881
+ ""
1882
+ ].join("\n");
1883
+ }
1845
1884
  function createSudaPreviewVitePlugin(root) {
1846
1885
  return {
1847
1886
  name: "suda-theme-preview",
@@ -1864,54 +1903,31 @@ function createSudaPreviewVitePlugin(root) {
1864
1903
  const acceptLanguageHeader = request.headers["accept-language"];
1865
1904
  const acceptLanguage = Array.isArray(acceptLanguageHeader) ? acceptLanguageHeader.join(",") : acceptLanguageHeader;
1866
1905
  const explicitLang = url.searchParams.get("lang");
1867
- const { theme, locale } = await loadLocalizedViteDevTheme(
1906
+ const localized = await loadLocalizedViteDevTheme(
1868
1907
  root,
1869
1908
  server,
1870
1909
  explicitLang,
1871
1910
  acceptLanguage
1872
1911
  );
1873
1912
  const renderer = await loadThemeScopedRenderer(root);
1874
- if (slug === null) {
1875
- const homeSlug = pickStarterSlug(theme);
1876
- if (!homeSlug) {
1877
- response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
1878
- response.end("No starter pages declared by this theme.");
1879
- return;
1880
- }
1881
- const starter2 = findStarterPage(theme, homeSlug);
1882
- if (!starter2) {
1883
- response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
1884
- response.end(`Unknown starter page: ${homeSlug}`);
1885
- return;
1886
- }
1887
- const html2 = await server.transformIndexHtml(
1888
- url.pathname,
1889
- renderDevStarterPageHtml(
1890
- theme,
1891
- starter2,
1892
- renderer,
1893
- locale,
1894
- canonicalLocale(explicitLang) !== null
1895
- )
1896
- );
1897
- response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
1898
- response.end(html2);
1899
- return;
1900
- }
1901
- const starter = findStarterPage(theme, slug);
1913
+ const starterSlug = slug ?? pickStarterSlug(localized.theme);
1914
+ const starter = starterSlug ? findStarterPage(localized.theme, starterSlug) : null;
1902
1915
  if (!starter) {
1903
1916
  response.writeHead(404, { "Content-Type": "text/plain; charset=utf-8" });
1904
- response.end(`Unknown starter page: ${slug}`);
1917
+ response.end(
1918
+ starterSlug ? `Unknown starter page: ${starterSlug}` : "No starter pages declared by this theme."
1919
+ );
1905
1920
  return;
1906
1921
  }
1907
1922
  const html = await server.transformIndexHtml(
1908
1923
  url.pathname,
1909
1924
  renderDevStarterPageHtml(
1910
- theme,
1925
+ localized.theme,
1911
1926
  starter,
1912
1927
  renderer,
1913
- locale,
1914
- canonicalLocale(explicitLang) !== null
1928
+ localized.locale,
1929
+ canonicalLocale(explicitLang) !== null,
1930
+ localized.previewLocaleMessages
1915
1931
  )
1916
1932
  );
1917
1933
  response.writeHead(200, { "Content-Type": "text/html; charset=utf-8" });
@@ -1939,13 +1955,13 @@ async function loadLocalizedViteDevTheme(root, server, explicitLang, acceptLangu
1939
1955
  if (!baseTheme.module.manifest.previewLocales) {
1940
1956
  return { theme: baseTheme, locale };
1941
1957
  }
1942
- const messages = await readDevPreviewLocaleMessages(root, locale);
1958
+ const previewLocaleMessages = await readDevPreviewLocaleMessages(root, locale);
1943
1959
  server.moduleGraph.invalidateAll();
1944
1960
  const theme = await runWithThemePreviewLocale(
1945
- messages,
1961
+ previewLocaleMessages,
1946
1962
  () => loadViteDevTheme(root, server)
1947
1963
  );
1948
- return { theme, locale };
1964
+ return { theme, locale, previewLocaleMessages };
1949
1965
  });
1950
1966
  }
1951
1967
  var vitePreviewLocaleLoadTails = /* @__PURE__ */ new WeakMap();
@@ -1997,6 +2013,9 @@ function resolveDevStarterSlug(pathname) {
1997
2013
  if (pathname === "/") {
1998
2014
  return null;
1999
2015
  }
2016
+ if (pathname.startsWith("/@")) {
2017
+ return void 0;
2018
+ }
2000
2019
  const normalized = pathname.replace(/^\/+/, "").replace(/\/+$/, "");
2001
2020
  if (!normalized || normalized.includes("/")) {
2002
2021
  return void 0;
@@ -2103,7 +2122,7 @@ async function loadThemeScopedRenderer(themeRoot) {
2103
2122
  renderToString: reactDomServerModule.renderToString
2104
2123
  };
2105
2124
  }
2106
- function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserveLang = false) {
2125
+ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserveLang = false, previewLocaleMessages) {
2107
2126
  const chrome = renderer.extractLayoutChrome(theme.module.defaultLayout);
2108
2127
  const metadata = {
2109
2128
  resolveAssetUrl: createPreviewAssetResolver(renderer.resolveAssetPath),
@@ -2121,6 +2140,16 @@ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserve
2121
2140
  const customHead = chrome.customHeadCode ?? "";
2122
2141
  const customBody = chrome.customBodyCode ?? "";
2123
2142
  const designPalette = renderDevDesignPalette(theme.module.manifest.designSystem);
2143
+ const hydrationPayload = serializeInlineScriptValue({
2144
+ pageData: page.data,
2145
+ layoutData: theme.module.defaultLayout,
2146
+ contactForm: PREVIEW_CONTACT_FORM,
2147
+ previewLocaleMessages
2148
+ });
2149
+ const hydrationScript = [
2150
+ `<script id="suda-theme-hydration-data" type="application/json">${hydrationPayload}</script>`,
2151
+ '<script type="module">void import("/.suda-build/preview-client.ts").catch((error)=>console.error("[theme-dev] theme client failed to load",error))</script>'
2152
+ ].join("");
2124
2153
  return [
2125
2154
  "<!doctype html>",
2126
2155
  `<html lang="${escapeHtml(locale)}">`,
@@ -2133,9 +2162,10 @@ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserve
2133
2162
  customHead,
2134
2163
  "</head>",
2135
2164
  `<body${cssVarStyle ? ` style="${cssVarStyle}"` : ""}>`,
2136
- body,
2165
+ `<div data-theme-hydration-root="" style="display:contents">${body}</div>`,
2137
2166
  designPalette.html,
2138
2167
  customBody,
2168
+ hydrationScript,
2139
2169
  ...preserveLang ? [
2140
2170
  '<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>'
2141
2171
  ] : [],
@@ -2154,19 +2184,28 @@ function renderDevDesignPalette(designSystem) {
2154
2184
  id: preset.id,
2155
2185
  variables: createThemeDesignCssVariables(preset.tokens)
2156
2186
  }));
2187
+ const selectedIcon = renderToStaticMarkup(
2188
+ createElement(CircleCheck, {
2189
+ className: "suda-dev-preset-selected-icon",
2190
+ width: 32,
2191
+ height: 32,
2192
+ "aria-hidden": true,
2193
+ focusable: false
2194
+ })
2195
+ );
2157
2196
  const buttons = designSystem.presets.map(
2158
2197
  (preset) => `<button type="button" data-suda-dev-preset="${escapeHtml(preset.id)}" title="${escapeHtml(
2159
2198
  preset.description ? `${preset.label} \u2014 ${preset.description}` : preset.label
2160
2199
  )}" aria-label="${escapeHtml(preset.label)}"><span style="background:${escapeHtml(
2161
2200
  preset.tokens.colors.primary
2162
- )}"></span></button>`
2201
+ )}"></span>${selectedIcon}</button>`
2163
2202
  ).join("");
2164
2203
  const config = serializeInlineScriptValue({
2165
2204
  defaultPresetId: designSystem.defaultPresetId,
2166
2205
  presets
2167
2206
  });
2168
2207
  return {
2169
- style: '<style data-suda-dev-palette-style>\n#suda-dev-palette{all:initial;position:fixed;z-index:2147483000;top:50%;right:16px;width:64px;max-height:calc(100svh - 32px);padding:8px 9px;overflow-x:hidden;overflow-y:auto;box-sizing:border-box;background:#fff;border:1px solid #d1d5db;border-radius:999px;box-shadow:0 8px 24px rgb(15 23 42 / 12%);transform:translateY(-50%);color-scheme:light;scrollbar-width:none;-ms-overflow-style:none}\n#suda-dev-palette::-webkit-scrollbar{display:none}\n#suda-dev-palette button{all:initial;position:relative;display:grid;width:44px;height:44px;box-sizing:border-box;cursor:pointer;border-radius:999px;place-items:center}\n#suda-dev-palette button span{display:block;width:32px;height:32px;border-radius:999px;transition:transform 120ms ease}\n#suda-dev-palette button:hover span{transform:scale(1.08)}\n#suda-dev-palette button[aria-pressed="true"] span{visibility:hidden}\n#suda-dev-palette button[aria-pressed="true"]::after{position:absolute;display:grid;width:32px;height:32px;color:var(--suda-dev-preset-primary);font:700 22px/1 ui-sans-serif,system-ui,sans-serif;content:"\u2713";place-items:center}\n#suda-dev-palette button:focus-visible{outline:2px solid #94a3b8;outline-offset:2px}\n@media(max-width:640px){#suda-dev-palette{right:8px;max-height:calc(100svh - 16px)}}\n@media(prefers-reduced-motion:reduce){#suda-dev-palette button span{transition:none}}\n</style>',
2208
+ style: '<style data-suda-dev-palette-style>\n#suda-dev-palette{all:initial;position:fixed;z-index:2147483000;top:50%;right:16px;width:64px;max-height:calc(100svh - 32px);padding:8px 9px;overflow-x:hidden;overflow-y:auto;box-sizing:border-box;background:#fff;border:1px solid #d1d5db;border-radius:999px;box-shadow:0 8px 24px rgb(15 23 42 / 12%);transform:translateY(-50%);color-scheme:light;scrollbar-width:none;-ms-overflow-style:none}\n#suda-dev-palette::-webkit-scrollbar{display:none}\n#suda-dev-palette button{all:initial;position:relative;display:grid;width:44px;height:44px;box-sizing:border-box;cursor:pointer;border-radius:999px;place-items:center}\n#suda-dev-palette button span{display:block;width:32px;height:32px;border-radius:999px;transition:transform 120ms ease}\n#suda-dev-palette button:hover span{transform:scale(1.08)}\n#suda-dev-palette button[aria-pressed="true"] span{visibility:hidden}\n#suda-dev-palette .suda-dev-preset-selected-icon{position:absolute;display:block;width:32px;height:32px;color:var(--suda-dev-preset-primary);visibility:hidden;opacity:0;transform:scale(.85);transition:opacity 120ms ease,transform 120ms ease}\n#suda-dev-palette button[aria-pressed="true"] .suda-dev-preset-selected-icon{visibility:visible;opacity:1;transform:scale(1)}\n#suda-dev-palette button:focus-visible{outline:2px solid #94a3b8;outline-offset:2px}\n@media(max-width:640px){#suda-dev-palette{right:8px;max-height:calc(100svh - 16px)}}\n@media(prefers-reduced-motion:reduce){#suda-dev-palette button span,#suda-dev-palette .suda-dev-preset-selected-icon{transition:none}}\n</style>',
2170
2209
  html: `<aside id="suda-dev-palette" aria-label="Design presets">${buttons}</aside><script data-suda-dev-palette-script>(function(){const config=${config};const palette=document.getElementById("suda-dev-palette");if(!palette)return;const byId=new Map(config.presets.map((preset)=>[preset.id,preset]));const params=new URL(location.href).searchParams;const requested=params.get("designPreset");const selected=byId.has(requested)?requested:config.defaultPresetId;function apply(id,updateUrl){const preset=byId.get(id);if(!preset)return;const properties=Object.keys(preset.variables);const targets=[document.documentElement,document.body,...document.querySelectorAll("[style]")].filter((element,index,items)=>items.indexOf(element)===index&&(element===document.documentElement||element===document.body||properties.some((property)=>element.style.getPropertyValue(property)!=="")));for(const target of targets){for(const [property,value] of Object.entries(preset.variables))target.style.setProperty(property,value)}for(const button of palette.querySelectorAll("[data-suda-dev-preset]")){const active=button.dataset.sudaDevPreset===id;button.setAttribute("aria-pressed",String(active));button.style.setProperty("--suda-dev-preset-primary",byId.get(button.dataset.sudaDevPreset).variables["--suda-color-primary"])}if(updateUrl){const url=new URL(location.href);url.searchParams.set("designPreset",id);history.replaceState(history.state,"",url)}}if(requested&&byId.has(requested))apply(selected,false);else for(const button of palette.querySelectorAll("[data-suda-dev-preset]")){button.setAttribute("aria-pressed",String(button.dataset.sudaDevPreset===selected));button.style.setProperty("--suda-dev-preset-primary",byId.get(button.dataset.sudaDevPreset).variables["--suda-color-primary"])}palette.addEventListener("click",(event)=>{const button=event.target.closest("[data-suda-dev-preset]");if(button)apply(button.dataset.sudaDevPreset,true)})})();</script>`
2171
2210
  };
2172
2211
  }