@sudajs/cli 0.18.3 → 0.18.5
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 +2 -0
- package/dist/index.js +134 -1
- package/dist/index.js.map +1 -1
- package/package.json +3 -3
- package/templates/theme/AGENTS.md +22 -4
- package/templates/theme/package.json +2 -2
package/dist/index.d.ts
CHANGED
|
@@ -1800,6 +1800,7 @@ declare function fetchJson<T>(url: string, options?: RequestInit & {
|
|
|
1800
1800
|
token?: string;
|
|
1801
1801
|
}): Promise<T>;
|
|
1802
1802
|
declare function validateThemeTailwindContract(root: string): Promise<void>;
|
|
1803
|
+
declare function validateThemeCssTokenContract(root: string): Promise<void>;
|
|
1803
1804
|
declare function validateTheme(root: string): Promise<ValidatedTheme>;
|
|
1804
1805
|
declare function validateThemeLocales(root: string): Promise<void>;
|
|
1805
1806
|
declare function validateThemePreviewLocales(root: string, previewLocales: string[] | undefined): Promise<void>;
|
|
@@ -1827,6 +1828,7 @@ declare const __testUtils: {
|
|
|
1827
1828
|
slugifyThemeName: typeof slugifyThemeName;
|
|
1828
1829
|
validateThemeKey: typeof validateThemeKey;
|
|
1829
1830
|
validateThemeName: typeof validateThemeName;
|
|
1831
|
+
validateThemeCssTokenContract: typeof validateThemeCssTokenContract;
|
|
1830
1832
|
validateThemeTailwindContract: typeof validateThemeTailwindContract;
|
|
1831
1833
|
validateThemeLocales: typeof validateThemeLocales;
|
|
1832
1834
|
validateThemePreviewLocales: typeof validateThemePreviewLocales;
|
package/dist/index.js
CHANGED
|
@@ -8,7 +8,7 @@ import { createInterface } from 'readline/promises';
|
|
|
8
8
|
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
|
-
import { themeDesignSystemSchema, createThemeAgentManifest, createSudaPageAgentRuntime, checkThemeModule, formatThemeCheckResult, agentValidationResultSchema, agentPageSchemaOutputSchema, agentComponentOutputSchema } from '@sudajs/theme-engine';
|
|
11
|
+
import { themeDesignSystemSchema, createThemeDesignCssVariables, createThemeAgentManifest, createSudaPageAgentRuntime, checkThemeModule, formatThemeCheckResult, agentValidationResultSchema, agentPageSchemaOutputSchema, agentComponentOutputSchema } from '@sudajs/theme-engine';
|
|
12
12
|
import { themeManifestSchema, runWithThemePreviewLocale } from '@sudajs/theme-engine/server';
|
|
13
13
|
import { Command } from 'commander';
|
|
14
14
|
import { build } from 'esbuild';
|
|
@@ -1123,6 +1123,108 @@ async function validateThemeTailwindContract(root) {
|
|
|
1123
1123
|
if (!styles.includes('@import "tailwindcss";') && !styles.includes("@import 'tailwindcss';")) {
|
|
1124
1124
|
throw new Error('src/styles.css must include `@import "tailwindcss";`.');
|
|
1125
1125
|
}
|
|
1126
|
+
await validateThemeCssTokenContract(root);
|
|
1127
|
+
}
|
|
1128
|
+
async function collectThemeSourceCssFiles(directory) {
|
|
1129
|
+
const entries = await readdir(directory, { withFileTypes: true });
|
|
1130
|
+
const files = [];
|
|
1131
|
+
for (const entry of entries) {
|
|
1132
|
+
const absolutePath = path2.join(directory, entry.name);
|
|
1133
|
+
if (entry.isDirectory()) {
|
|
1134
|
+
files.push(...await collectThemeSourceCssFiles(absolutePath));
|
|
1135
|
+
} else if (entry.isFile() && entry.name.endsWith(".css")) {
|
|
1136
|
+
files.push(absolutePath);
|
|
1137
|
+
}
|
|
1138
|
+
}
|
|
1139
|
+
return files;
|
|
1140
|
+
}
|
|
1141
|
+
function findThemeInlineBlocks(styles, filePath) {
|
|
1142
|
+
const blocks = [];
|
|
1143
|
+
const pattern = /@theme\s+inline\s*\{/g;
|
|
1144
|
+
for (const match of styles.matchAll(pattern)) {
|
|
1145
|
+
const start = match.index;
|
|
1146
|
+
let depth = 1;
|
|
1147
|
+
let cursor = start + match[0].length;
|
|
1148
|
+
while (cursor < styles.length && depth > 0) {
|
|
1149
|
+
if (styles[cursor] === "{") depth += 1;
|
|
1150
|
+
if (styles[cursor] === "}") depth -= 1;
|
|
1151
|
+
cursor += 1;
|
|
1152
|
+
}
|
|
1153
|
+
if (depth !== 0) {
|
|
1154
|
+
throw new Error(`${filePath} contains an unclosed \`@theme inline\` block.`);
|
|
1155
|
+
}
|
|
1156
|
+
blocks.push({
|
|
1157
|
+
body: styles.slice(start + match[0].length, cursor - 1),
|
|
1158
|
+
end: cursor,
|
|
1159
|
+
start
|
|
1160
|
+
});
|
|
1161
|
+
}
|
|
1162
|
+
return blocks;
|
|
1163
|
+
}
|
|
1164
|
+
async function validateThemeCssTokenContract(root) {
|
|
1165
|
+
const sourceRoot = path2.join(root, "src");
|
|
1166
|
+
const files = await collectThemeSourceCssFiles(sourceRoot);
|
|
1167
|
+
const sources = await Promise.all(
|
|
1168
|
+
files.map(async (filePath) => {
|
|
1169
|
+
const styles = await readFile(filePath, "utf8");
|
|
1170
|
+
return { blocks: findThemeInlineBlocks(styles, filePath), filePath, styles };
|
|
1171
|
+
})
|
|
1172
|
+
);
|
|
1173
|
+
const declarations = /* @__PURE__ */ new Map();
|
|
1174
|
+
for (const source of sources) {
|
|
1175
|
+
for (const block of source.blocks) {
|
|
1176
|
+
for (const match of block.body.matchAll(/(--[a-zA-Z0-9_-]+)\s*:\s*([^;]+);/g)) {
|
|
1177
|
+
const name = match[1];
|
|
1178
|
+
const value = match[2];
|
|
1179
|
+
if (name !== void 0 && value !== void 0) {
|
|
1180
|
+
declarations.set(name, value);
|
|
1181
|
+
}
|
|
1182
|
+
}
|
|
1183
|
+
}
|
|
1184
|
+
}
|
|
1185
|
+
const sudaAliases = /* @__PURE__ */ new Set();
|
|
1186
|
+
let changed = true;
|
|
1187
|
+
while (changed) {
|
|
1188
|
+
changed = false;
|
|
1189
|
+
for (const [name, value] of declarations) {
|
|
1190
|
+
const dependsOnSuda = /var\(\s*--suda-/.test(value);
|
|
1191
|
+
const dependsOnAlias = [...sudaAliases].some(
|
|
1192
|
+
(alias) => new RegExp(`var\\(\\s*${escapeRegExp(alias)}(?:\\s*[,\\)])`).test(value)
|
|
1193
|
+
);
|
|
1194
|
+
if ((dependsOnSuda || dependsOnAlias) && !sudaAliases.has(name)) {
|
|
1195
|
+
sudaAliases.add(name);
|
|
1196
|
+
changed = true;
|
|
1197
|
+
}
|
|
1198
|
+
}
|
|
1199
|
+
}
|
|
1200
|
+
const issues = [];
|
|
1201
|
+
for (const source of sources) {
|
|
1202
|
+
const handwrittenStyles = source.styles.split("");
|
|
1203
|
+
for (const block of source.blocks) {
|
|
1204
|
+
for (let index = block.start; index < block.end; index += 1) {
|
|
1205
|
+
if (handwrittenStyles[index] !== "\n") {
|
|
1206
|
+
handwrittenStyles[index] = " ";
|
|
1207
|
+
}
|
|
1208
|
+
}
|
|
1209
|
+
}
|
|
1210
|
+
const searchable = handwrittenStyles.join("");
|
|
1211
|
+
for (const alias of sudaAliases) {
|
|
1212
|
+
const reference = new RegExp(`var\\(\\s*${escapeRegExp(alias)}(?:\\s*[,\\)])`, "g");
|
|
1213
|
+
for (const match of searchable.matchAll(reference)) {
|
|
1214
|
+
const line = searchable.slice(0, match.index ?? 0).split("\n").length;
|
|
1215
|
+
issues.push(`${path2.relative(root, source.filePath)}:${line} uses ${alias}`);
|
|
1216
|
+
}
|
|
1217
|
+
}
|
|
1218
|
+
}
|
|
1219
|
+
if (issues.length > 0) {
|
|
1220
|
+
throw new Error(
|
|
1221
|
+
[
|
|
1222
|
+
"Hand-written theme CSS must not consume Suda-backed Tailwind aliases from `@theme inline`.",
|
|
1223
|
+
...issues.map((issue) => ` - ${issue}`),
|
|
1224
|
+
"Use `var(--suda-*)` directly, or define and consume a theme-prefixed variable on the theme root. Tailwind utilities such as `bg-primary` remain supported."
|
|
1225
|
+
].join("\n")
|
|
1226
|
+
);
|
|
1227
|
+
}
|
|
1126
1228
|
}
|
|
1127
1229
|
async function validateTheme(root) {
|
|
1128
1230
|
const packageJsonPath = path2.join(root, "package.json");
|
|
@@ -1657,6 +1759,7 @@ var __testUtils = {
|
|
|
1657
1759
|
slugifyThemeName,
|
|
1658
1760
|
validateThemeKey,
|
|
1659
1761
|
validateThemeName,
|
|
1762
|
+
validateThemeCssTokenContract,
|
|
1660
1763
|
validateThemeTailwindContract,
|
|
1661
1764
|
validateThemeLocales,
|
|
1662
1765
|
validateThemePreviewLocales,
|
|
@@ -2018,6 +2121,7 @@ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserve
|
|
|
2018
2121
|
const cssVarStyle = Object.entries(chrome.cssVariables).map(([key, value]) => `${key}: ${value};`).join(" ");
|
|
2019
2122
|
const customHead = chrome.customHeadCode ?? "";
|
|
2020
2123
|
const customBody = chrome.customBodyCode ?? "";
|
|
2124
|
+
const designPalette = renderDevDesignPalette(theme.module.manifest.designSystem);
|
|
2021
2125
|
return [
|
|
2022
2126
|
"<!doctype html>",
|
|
2023
2127
|
`<html lang="${escapeHtml(locale)}">`,
|
|
@@ -2026,10 +2130,12 @@ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserve
|
|
|
2026
2130
|
'<meta name="viewport" content="width=device-width, initial-scale=1" />',
|
|
2027
2131
|
`<title>${escapeHtml(`${theme.module.manifest.name} \u2014 ${page.title}`)}</title>`,
|
|
2028
2132
|
'<link rel="stylesheet" href="/src/styles.css" />',
|
|
2133
|
+
designPalette.style,
|
|
2029
2134
|
customHead,
|
|
2030
2135
|
"</head>",
|
|
2031
2136
|
`<body${cssVarStyle ? ` style="${cssVarStyle}"` : ""}>`,
|
|
2032
2137
|
body,
|
|
2138
|
+
designPalette.html,
|
|
2033
2139
|
customBody,
|
|
2034
2140
|
...preserveLang ? [
|
|
2035
2141
|
'<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>'
|
|
@@ -2038,6 +2144,33 @@ function renderDevStarterPageHtml(theme, page, renderer, locale = "en", preserve
|
|
|
2038
2144
|
"</html>"
|
|
2039
2145
|
].join("");
|
|
2040
2146
|
}
|
|
2147
|
+
function serializeInlineScriptValue(value) {
|
|
2148
|
+
return JSON.stringify(value).replaceAll("<", "\\u003c").replaceAll("\u2028", "\\u2028").replaceAll("\u2029", "\\u2029");
|
|
2149
|
+
}
|
|
2150
|
+
function renderDevDesignPalette(designSystem) {
|
|
2151
|
+
if (designSystem.presets.length < 2) {
|
|
2152
|
+
return { style: "", html: "" };
|
|
2153
|
+
}
|
|
2154
|
+
const presets = designSystem.presets.map((preset) => ({
|
|
2155
|
+
id: preset.id,
|
|
2156
|
+
variables: createThemeDesignCssVariables(preset.tokens)
|
|
2157
|
+
}));
|
|
2158
|
+
const buttons = designSystem.presets.map(
|
|
2159
|
+
(preset) => `<button type="button" data-suda-dev-preset="${escapeHtml(preset.id)}" title="${escapeHtml(
|
|
2160
|
+
preset.description ? `${preset.label} \u2014 ${preset.description}` : preset.label
|
|
2161
|
+
)}" aria-label="${escapeHtml(preset.label)}"><span style="background:${escapeHtml(
|
|
2162
|
+
preset.tokens.colors.primary
|
|
2163
|
+
)}"></span></button>`
|
|
2164
|
+
).join("");
|
|
2165
|
+
const config = serializeInlineScriptValue({
|
|
2166
|
+
defaultPresetId: designSystem.defaultPresetId,
|
|
2167
|
+
presets
|
|
2168
|
+
});
|
|
2169
|
+
return {
|
|
2170
|
+
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>',
|
|
2171
|
+
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>`
|
|
2172
|
+
};
|
|
2173
|
+
}
|
|
2041
2174
|
var SCREENSHOT_DEVICES = ["desktop", "tablet", "mobile"];
|
|
2042
2175
|
var SCREENSHOT_DEVICE_CONFIG = {
|
|
2043
2176
|
desktop: {
|