meno-core 1.1.3 → 1.1.4
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/chunks/{chunk-LOZL5HOF.js → chunk-UOF4MCAD.js} +64 -2
- package/dist/chunks/chunk-UOF4MCAD.js.map +7 -0
- package/dist/chunks/{chunk-3JXK2QFU.js → chunk-ZEDLSHYQ.js} +2 -2
- package/dist/lib/client/index.js +46 -28
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +1267 -233
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +3 -1
- package/dist/lib/shared/index.js.map +1 -1
- package/lib/client/core/ComponentBuilder.test.ts +32 -0
- package/lib/client/core/ComponentBuilder.ts +30 -0
- package/lib/client/theme.test.ts +2 -2
- package/lib/client/theme.ts +28 -26
- package/lib/server/createServer.ts +5 -1
- package/lib/server/cssAudit.test.ts +270 -0
- package/lib/server/cssAudit.ts +815 -0
- package/lib/server/deMirror.test.ts +61 -0
- package/lib/server/deMirror.ts +317 -0
- package/lib/server/fileWatcher.test.ts +23 -0
- package/lib/server/fileWatcher.ts +6 -1
- package/lib/server/index.ts +19 -0
- package/lib/server/routes/api/core-routes.ts +18 -0
- package/lib/server/routes/api/cssAudit.test.ts +101 -0
- package/lib/server/routes/api/cssAudit.ts +138 -0
- package/lib/server/routes/api/deMirror.test.ts +105 -0
- package/lib/server/routes/api/deMirror.ts +127 -0
- package/lib/server/routes/api/pages.ts +2 -2
- package/lib/server/services/componentService.test.ts +43 -0
- package/lib/server/services/componentService.ts +49 -12
- package/lib/server/services/pageService.test.ts +15 -0
- package/lib/server/services/pageService.ts +44 -22
- package/lib/server/ssr/htmlGenerator.test.ts +29 -0
- package/lib/server/ssr/htmlGenerator.ts +83 -3
- package/lib/server/themeCssCodec.test.ts +67 -0
- package/lib/server/themeCssCodec.ts +67 -5
- package/lib/server/webflow/templateWrapper.ts +54 -0
- package/lib/shared/cssGeneration.test.ts +28 -0
- package/lib/shared/interfaces/contentProvider.ts +9 -0
- package/lib/shared/types/cms.ts +1 -0
- package/lib/shared/utilityClassConfig.ts +2 -0
- package/lib/shared/utilityClassMapper.test.ts +53 -0
- package/lib/shared/utilityClassNames.ts +74 -0
- package/lib/shared/utils/fileUtils.ts +11 -0
- package/lib/shared/validation/schemas.ts +1 -0
- package/lib/shared/viewportUnits.test.ts +34 -1
- package/lib/shared/viewportUnits.ts +43 -0
- package/package.json +3 -1
- package/vite.config.ts +4 -1
- package/dist/chunks/chunk-LOZL5HOF.js.map +0 -7
- /package/dist/chunks/{chunk-3JXK2QFU.js.map → chunk-ZEDLSHYQ.js.map} +0 -0
package/dist/lib/server/index.js
CHANGED
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
processStructure,
|
|
34
34
|
resolveHtmlMapping,
|
|
35
35
|
skipEmptyTemplateAttributes
|
|
36
|
-
} from "../../chunks/chunk-
|
|
36
|
+
} from "../../chunks/chunk-ZEDLSHYQ.js";
|
|
37
37
|
import {
|
|
38
38
|
DEFAULT_BREAKPOINTS,
|
|
39
39
|
DEFAULT_FLUID_RANGE,
|
|
@@ -86,13 +86,14 @@ import {
|
|
|
86
86
|
resolveTemplateRawValue,
|
|
87
87
|
responsiveStylesToClasses,
|
|
88
88
|
rewriteViewportUnits,
|
|
89
|
+
rewriteViewportUnitsInStylesheet,
|
|
89
90
|
scalePropertyValue,
|
|
90
91
|
singularize,
|
|
91
92
|
toFriendlyError,
|
|
92
93
|
validateCMSDraftItem,
|
|
93
94
|
validateCMSItem,
|
|
94
95
|
validateComponentDefinition
|
|
95
|
-
} from "../../chunks/chunk-
|
|
96
|
+
} from "../../chunks/chunk-UOF4MCAD.js";
|
|
96
97
|
import {
|
|
97
98
|
DEFAULT_I18N_CONFIG,
|
|
98
99
|
buildLocalizedPath,
|
|
@@ -165,10 +166,10 @@ var WebSocketManager = class {
|
|
|
165
166
|
/**
|
|
166
167
|
* Broadcast update notification
|
|
167
168
|
*/
|
|
168
|
-
broadcastUpdate(
|
|
169
|
+
broadcastUpdate(path5) {
|
|
169
170
|
this.broadcast({
|
|
170
171
|
type: "hmr:update",
|
|
171
|
-
path:
|
|
172
|
+
path: path5 || "all"
|
|
172
173
|
});
|
|
173
174
|
}
|
|
174
175
|
/**
|
|
@@ -1518,8 +1519,8 @@ function cachedJsonResponse(req, body, options = {}) {
|
|
|
1518
1519
|
}
|
|
1519
1520
|
|
|
1520
1521
|
// lib/server/routes/api/pages.ts
|
|
1521
|
-
function handlePagesRoute(pageService) {
|
|
1522
|
-
const pages = pageService.getAllPagesWithInfo();
|
|
1522
|
+
async function handlePagesRoute(pageService) {
|
|
1523
|
+
const pages = await pageService.getAllPagesWithInfo();
|
|
1523
1524
|
return jsonResponse({ pages });
|
|
1524
1525
|
}
|
|
1525
1526
|
function handlePageDataRoute(url, pageService) {
|
|
@@ -1641,11 +1642,11 @@ function handleComponentUsageRoute(pageService, componentService) {
|
|
|
1641
1642
|
}
|
|
1642
1643
|
}
|
|
1643
1644
|
const pagePaths = pageService.getAllPagePaths();
|
|
1644
|
-
for (const
|
|
1645
|
-
const pageData = pageService.getPageData(
|
|
1645
|
+
for (const path5 of pagePaths) {
|
|
1646
|
+
const pageData = pageService.getPageData(path5);
|
|
1646
1647
|
if (pageData && "root" in pageData && pageData.root) {
|
|
1647
|
-
const name =
|
|
1648
|
-
walkTree(pageData.root, name,
|
|
1648
|
+
const name = path5 === "/" ? "index" : path5.replace(/^\//, "").replace(/\//g, "-");
|
|
1649
|
+
walkTree(pageData.root, name, path5, "page");
|
|
1649
1650
|
}
|
|
1650
1651
|
}
|
|
1651
1652
|
const allComponents = componentService.getAllComponents();
|
|
@@ -1755,9 +1756,24 @@ function sectionLabelForGroup(group) {
|
|
|
1755
1756
|
if (!group || group === "other") return OTHER_LABEL;
|
|
1756
1757
|
return VARIABLE_GROUPS.find((g) => g.value === group)?.label ?? OTHER_LABEL;
|
|
1757
1758
|
}
|
|
1758
|
-
|
|
1759
|
+
function normalizeSectionLabel(s) {
|
|
1760
|
+
return s.trim().toLowerCase().replace(/[-–—]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1761
|
+
}
|
|
1762
|
+
var NORMALIZED_LABEL_TO_GROUP = new Map(
|
|
1763
|
+
VARIABLE_GROUPS.map((g) => [normalizeSectionLabel(g.label), g.value])
|
|
1764
|
+
);
|
|
1765
|
+
var PREFIX_LABELS = VARIABLE_GROUPS.filter((g) => g.value !== "other").map((g) => ({ norm: normalizeSectionLabel(g.label), group: g.value })).sort((a, b) => b.norm.length - a.norm.length);
|
|
1759
1766
|
function groupForSectionLabel(label) {
|
|
1760
|
-
|
|
1767
|
+
const norm = normalizeSectionLabel(label);
|
|
1768
|
+
const exact = NORMALIZED_LABEL_TO_GROUP.get(norm);
|
|
1769
|
+
if (exact) return exact;
|
|
1770
|
+
for (const { norm: labelNorm, group } of PREFIX_LABELS) {
|
|
1771
|
+
if (norm.startsWith(labelNorm)) {
|
|
1772
|
+
const next = norm.charAt(labelNorm.length);
|
|
1773
|
+
if (next === "" || !/[a-z0-9]/.test(next)) return group;
|
|
1774
|
+
}
|
|
1775
|
+
}
|
|
1776
|
+
return null;
|
|
1761
1777
|
}
|
|
1762
1778
|
function isColorValue(value) {
|
|
1763
1779
|
const v = value.trim().toLowerCase();
|
|
@@ -1766,6 +1782,11 @@ function isColorValue(value) {
|
|
|
1766
1782
|
if (v === "transparent" || v === "currentcolor") return true;
|
|
1767
1783
|
return false;
|
|
1768
1784
|
}
|
|
1785
|
+
var NAME_HINT_GROUPS = [
|
|
1786
|
+
{ needle: /(^|[-_])tracking([-_]|$)/, group: "letter-spacing" },
|
|
1787
|
+
{ needle: /(^|[-_])leading([-_]|$)/, group: "line-height" },
|
|
1788
|
+
{ needle: /(^|[-_])(radius|rounded)([-_]|$)/, group: "border-radius" }
|
|
1789
|
+
];
|
|
1769
1790
|
function inferGroup(cssVar, value) {
|
|
1770
1791
|
const v = value.trim().toLowerCase();
|
|
1771
1792
|
if (/^\d+(\.\d+)?m?s$/.test(v)) return "duration";
|
|
@@ -1776,6 +1797,9 @@ function inferGroup(cssVar, value) {
|
|
|
1776
1797
|
}
|
|
1777
1798
|
if (/^\d*\.\d+$/.test(v) || /^[1-9](\.\d+)?$/.test(v)) return "line-height";
|
|
1778
1799
|
const name = cssVar.replace(/^--/, "").toLowerCase();
|
|
1800
|
+
for (const { needle, group } of NAME_HINT_GROUPS) {
|
|
1801
|
+
if (needle.test(name)) return group;
|
|
1802
|
+
}
|
|
1779
1803
|
for (const g of VARIABLE_GROUPS) {
|
|
1780
1804
|
if (g.value !== "other" && name.includes(g.value)) return g.value;
|
|
1781
1805
|
}
|
|
@@ -2057,8 +2081,7 @@ function parseThemeCss(css, breakpoints) {
|
|
|
2057
2081
|
const dn = inner.match(/^colors\s*:\s*([\w-]+)/i);
|
|
2058
2082
|
if (dn) defaultName = dn[1];
|
|
2059
2083
|
} else {
|
|
2060
|
-
|
|
2061
|
-
if (g) section = g;
|
|
2084
|
+
section = groupForSectionLabel(inner);
|
|
2062
2085
|
}
|
|
2063
2086
|
continue;
|
|
2064
2087
|
}
|
|
@@ -2134,9 +2157,9 @@ function enqueue(fn) {
|
|
|
2134
2157
|
);
|
|
2135
2158
|
return run;
|
|
2136
2159
|
}
|
|
2137
|
-
async function readJsonSafe(
|
|
2160
|
+
async function readJsonSafe(path5) {
|
|
2138
2161
|
try {
|
|
2139
|
-
return JSON.parse(await readTextFile(
|
|
2162
|
+
return JSON.parse(await readTextFile(path5));
|
|
2140
2163
|
} catch {
|
|
2141
2164
|
return null;
|
|
2142
2165
|
}
|
|
@@ -2148,9 +2171,9 @@ function emptyModel() {
|
|
|
2148
2171
|
return { themes: { default: "default", themes: { default: { colors: {} } } }, variables: [], raw: [] };
|
|
2149
2172
|
}
|
|
2150
2173
|
async function readModel(root) {
|
|
2151
|
-
const
|
|
2152
|
-
if (!await fileExists(
|
|
2153
|
-
return parseThemeCss(await readTextFile(
|
|
2174
|
+
const path5 = themeCssPath(root);
|
|
2175
|
+
if (!await fileExists(path5)) return emptyModel();
|
|
2176
|
+
return parseThemeCss(await readTextFile(path5), configService.getBreakpoints());
|
|
2154
2177
|
}
|
|
2155
2178
|
async function writeModel(root, model) {
|
|
2156
2179
|
const css = serializeThemeCss(model, scaleConfig());
|
|
@@ -2880,8 +2903,880 @@ async function handleItemRoute(collection, slug, cmsService) {
|
|
|
2880
2903
|
return jsonResponse({ item: match.item });
|
|
2881
2904
|
}
|
|
2882
2905
|
|
|
2883
|
-
// lib/server/routes/api/
|
|
2906
|
+
// lib/server/routes/api/cssAudit.ts
|
|
2907
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
2884
2908
|
import path from "node:path";
|
|
2909
|
+
|
|
2910
|
+
// lib/server/cssAudit.ts
|
|
2911
|
+
import * as csstree from "css-tree";
|
|
2912
|
+
var DEFAULT_UTILITY_HINTS = [
|
|
2913
|
+
// Spacing / sizing
|
|
2914
|
+
"^(margin|padding|gap)(-|$)",
|
|
2915
|
+
"^no-(padding|margin|overflow)",
|
|
2916
|
+
"(^|-)auto-margin$",
|
|
2917
|
+
"^(max|min)-(w|h|width|height)",
|
|
2918
|
+
"^(w|h)-[0-9]",
|
|
2919
|
+
"^size-[0-9]",
|
|
2920
|
+
"^(small|medium|large)(-|$)",
|
|
2921
|
+
// Layout / display
|
|
2922
|
+
"^align(-|$)",
|
|
2923
|
+
"^justify(-|$)",
|
|
2924
|
+
"^(grid|col|flex)-",
|
|
2925
|
+
"^container(-|$)",
|
|
2926
|
+
"(^|-)(overflow|opacity|relative|absolute|sticky|inline|centered)(-|$)",
|
|
2927
|
+
"^(hide|show|hidden|visible)$",
|
|
2928
|
+
// State / Webflow combo classes
|
|
2929
|
+
"^is--?",
|
|
2930
|
+
"^has--?",
|
|
2931
|
+
"^on-h[0-9]",
|
|
2932
|
+
"^type-[0-9]+$",
|
|
2933
|
+
"(^|-)active$",
|
|
2934
|
+
// Colors
|
|
2935
|
+
"(^|-)(white|black|dark|light|primary|secondary|grey|gray)(-|$)",
|
|
2936
|
+
// Cross-cutting UI
|
|
2937
|
+
"(^|-)arrow",
|
|
2938
|
+
// Relume / newer-Webflow utility families (text-size-*, heading-style-*, spacer-*, max-width-*)
|
|
2939
|
+
"^text-(size|align|color|weight|style)",
|
|
2940
|
+
"^heading-style",
|
|
2941
|
+
"^(spacer|max-width|background-color)(-|$)"
|
|
2942
|
+
];
|
|
2943
|
+
var DEFAULT_WEBFLOW_AUTO = [
|
|
2944
|
+
"^(div-block|text-block|link-block|bold-text|block-quote|rich-text-block|html-embed)(-\\d+)?$",
|
|
2945
|
+
"^(heading|paragraph|image|list|list-item|figure|grid|columns?|field-label|text-field)-\\d+$"
|
|
2946
|
+
];
|
|
2947
|
+
var CLASS_TOKEN_RE = /\.(-?[_a-zA-Z][\w-]*)/g;
|
|
2948
|
+
var FUNCTIONAL_PSEUDO_RE = /:(not|is|where|has|matches)\([^()]*\)/gi;
|
|
2949
|
+
function emptyBucket() {
|
|
2950
|
+
return { classes: 0, rules: 0, declarations: 0, approxBytes: 0 };
|
|
2951
|
+
}
|
|
2952
|
+
function classTokens(selector) {
|
|
2953
|
+
const out = [];
|
|
2954
|
+
let m;
|
|
2955
|
+
CLASS_TOKEN_RE.lastIndex = 0;
|
|
2956
|
+
while ((m = CLASS_TOKEN_RE.exec(selector)) !== null) {
|
|
2957
|
+
if (m[1]) out.push(m[1]);
|
|
2958
|
+
}
|
|
2959
|
+
return out;
|
|
2960
|
+
}
|
|
2961
|
+
function sectionPrefix(name) {
|
|
2962
|
+
const seg = name.split(/[-_]/)[0] ?? name;
|
|
2963
|
+
const stripped = seg.replace(/\d+$/, "");
|
|
2964
|
+
return stripped.length > 0 ? stripped : seg;
|
|
2965
|
+
}
|
|
2966
|
+
function commonPrefixLength(a, b) {
|
|
2967
|
+
const max = Math.min(a.length, b.length);
|
|
2968
|
+
let i = 0;
|
|
2969
|
+
while (i < max && a[i] === b[i]) i++;
|
|
2970
|
+
return i;
|
|
2971
|
+
}
|
|
2972
|
+
function editDistance(a, b) {
|
|
2973
|
+
const m = a.length;
|
|
2974
|
+
const n = b.length;
|
|
2975
|
+
if (m === 0) return n;
|
|
2976
|
+
if (n === 0) return m;
|
|
2977
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
2978
|
+
let curr = new Array(n + 1).fill(0);
|
|
2979
|
+
for (let i = 1; i <= m; i++) {
|
|
2980
|
+
curr[0] = i;
|
|
2981
|
+
for (let j = 1; j <= n; j++) {
|
|
2982
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
2983
|
+
curr[j] = Math.min((prev[j] ?? 0) + 1, (curr[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
2984
|
+
}
|
|
2985
|
+
[prev, curr] = [curr, prev];
|
|
2986
|
+
}
|
|
2987
|
+
return prev[n] ?? 0;
|
|
2988
|
+
}
|
|
2989
|
+
function nearestKeptPrefix(prefix, kept) {
|
|
2990
|
+
let best = null;
|
|
2991
|
+
let bestShared = -1;
|
|
2992
|
+
for (const candidate of kept) {
|
|
2993
|
+
if (Math.abs(candidate.length - prefix.length) > 1) continue;
|
|
2994
|
+
const shared = commonPrefixLength(prefix, candidate);
|
|
2995
|
+
if (shared < 2) continue;
|
|
2996
|
+
if (editDistance(prefix, candidate) !== 1) continue;
|
|
2997
|
+
if (shared > bestShared) {
|
|
2998
|
+
bestShared = shared;
|
|
2999
|
+
best = candidate;
|
|
3000
|
+
}
|
|
3001
|
+
}
|
|
3002
|
+
return best;
|
|
3003
|
+
}
|
|
3004
|
+
function analyzeSelector(selector) {
|
|
3005
|
+
const cleaned = selector.replace(FUNCTIONAL_PSEUDO_RE, " ");
|
|
3006
|
+
const segments = cleaned.split(/\s*[>+~]\s*|\s+/).filter((s) => s.trim().length > 0);
|
|
3007
|
+
const subject = segments[segments.length - 1] ?? cleaned;
|
|
3008
|
+
const subjectClasses = classTokens(subject);
|
|
3009
|
+
const allClasses = classTokens(selector);
|
|
3010
|
+
const compound = segments.length > 1 || subjectClasses.length > 1;
|
|
3011
|
+
return { allClasses, subjectClasses, compound };
|
|
3012
|
+
}
|
|
3013
|
+
function countDeclarations(block) {
|
|
3014
|
+
let count = 0;
|
|
3015
|
+
if (block && block.type === "Block") {
|
|
3016
|
+
block.children.forEach((child) => {
|
|
3017
|
+
if (child.type === "Declaration") count++;
|
|
3018
|
+
});
|
|
3019
|
+
}
|
|
3020
|
+
return count;
|
|
3021
|
+
}
|
|
3022
|
+
function extractRules(css) {
|
|
3023
|
+
const ast = csstree.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3024
|
+
const out = [];
|
|
3025
|
+
const visit = (container, media, insideSkippedAtrule) => {
|
|
3026
|
+
const children = "children" in container ? container.children : null;
|
|
3027
|
+
if (!children) return;
|
|
3028
|
+
children.forEach((node) => {
|
|
3029
|
+
if (node.type === "Atrule") {
|
|
3030
|
+
const name = node.name.toLowerCase();
|
|
3031
|
+
if (name === "media") {
|
|
3032
|
+
const prelude = node.prelude ? csstree.generate(node.prelude) : "";
|
|
3033
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3034
|
+
} else if (name === "supports") {
|
|
3035
|
+
if (node.block) visit(node.block, media, false);
|
|
3036
|
+
} else if (node.block) {
|
|
3037
|
+
visit(node.block, media, true);
|
|
3038
|
+
}
|
|
3039
|
+
return;
|
|
3040
|
+
}
|
|
3041
|
+
if (node.type !== "Rule" || insideSkippedAtrule) return;
|
|
3042
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3043
|
+
const decls = countDeclarations(node.block);
|
|
3044
|
+
const ruleBytes = csstree.generate(node).length;
|
|
3045
|
+
const classSelectors = [];
|
|
3046
|
+
node.prelude.children.forEach((selectorNode) => {
|
|
3047
|
+
const text = csstree.generate(selectorNode);
|
|
3048
|
+
const shape = analyzeSelector(text);
|
|
3049
|
+
if (shape.allClasses.length > 0) classSelectors.push({ text, shape });
|
|
3050
|
+
});
|
|
3051
|
+
if (classSelectors.length === 0) return;
|
|
3052
|
+
const perBytes = Math.max(1, Math.round(ruleBytes / classSelectors.length));
|
|
3053
|
+
for (const { text, shape } of classSelectors) {
|
|
3054
|
+
out.push({
|
|
3055
|
+
selector: text,
|
|
3056
|
+
media,
|
|
3057
|
+
classes: shape.allClasses,
|
|
3058
|
+
subjectClasses: shape.subjectClasses,
|
|
3059
|
+
declarations: decls,
|
|
3060
|
+
bytes: perBytes,
|
|
3061
|
+
compound: shape.compound
|
|
3062
|
+
});
|
|
3063
|
+
}
|
|
3064
|
+
});
|
|
3065
|
+
};
|
|
3066
|
+
visit(ast, null, false);
|
|
3067
|
+
return out;
|
|
3068
|
+
}
|
|
3069
|
+
function auditStylesheet(input) {
|
|
3070
|
+
const { css, pages, options = {} } = input;
|
|
3071
|
+
const selectorSampleLimit = options.selectorSampleLimit ?? 5;
|
|
3072
|
+
const flagLimit = options.flagLimit ?? 200;
|
|
3073
|
+
const minSectionClasses = options.minSectionClasses ?? 3;
|
|
3074
|
+
const miscBucketName = options.miscBucketName ?? "misc";
|
|
3075
|
+
const mergeTypos = options.mergeTypos ?? true;
|
|
3076
|
+
const utilityHints = (options.utilityHints ?? DEFAULT_UTILITY_HINTS).map((s) => new RegExp(s));
|
|
3077
|
+
const webflowAutoPatterns = (options.webflowAuto ?? DEFAULT_WEBFLOW_AUTO).map((s) => new RegExp(s));
|
|
3078
|
+
const pageCount = pages.length;
|
|
3079
|
+
const sharedMinPages = options.sharedMinPages ?? Math.max(2, Math.ceil(pageCount / 2));
|
|
3080
|
+
const pageUseCount = /* @__PURE__ */ new Map();
|
|
3081
|
+
for (const page of pages) {
|
|
3082
|
+
for (const cls of new Set(page.classes)) {
|
|
3083
|
+
pageUseCount.set(cls, (pageUseCount.get(cls) ?? 0) + 1);
|
|
3084
|
+
}
|
|
3085
|
+
}
|
|
3086
|
+
const ast = csstree.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3087
|
+
const classMap = /* @__PURE__ */ new Map();
|
|
3088
|
+
const rules = [];
|
|
3089
|
+
const breakpoints = [];
|
|
3090
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
3091
|
+
const base = emptyBucket();
|
|
3092
|
+
let totalRules = 0;
|
|
3093
|
+
let totalSelectors = 0;
|
|
3094
|
+
let totalDeclarations = 0;
|
|
3095
|
+
const accumFor = (name) => {
|
|
3096
|
+
let a = classMap.get(name);
|
|
3097
|
+
if (!a) {
|
|
3098
|
+
a = { name, ruleCount: 0, declarations: 0, breakpoints: /* @__PURE__ */ new Set(), selectors: [], ownsSimple: false };
|
|
3099
|
+
classMap.set(name, a);
|
|
3100
|
+
}
|
|
3101
|
+
return a;
|
|
3102
|
+
};
|
|
3103
|
+
const visit = (container, media, insideSkippedAtrule) => {
|
|
3104
|
+
const children = "children" in container ? container.children : null;
|
|
3105
|
+
if (!children) return;
|
|
3106
|
+
children.forEach((node) => {
|
|
3107
|
+
if (node.type === "Atrule") {
|
|
3108
|
+
const name = node.name.toLowerCase();
|
|
3109
|
+
if (name === "media") {
|
|
3110
|
+
const prelude = node.prelude ? csstree.generate(node.prelude) : "";
|
|
3111
|
+
if (!seenMedia.has(prelude)) {
|
|
3112
|
+
seenMedia.add(prelude);
|
|
3113
|
+
breakpoints.push(prelude);
|
|
3114
|
+
}
|
|
3115
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3116
|
+
} else if (name === "supports") {
|
|
3117
|
+
if (node.block) visit(node.block, media, false);
|
|
3118
|
+
} else if (node.block) {
|
|
3119
|
+
visit(node.block, media, true);
|
|
3120
|
+
}
|
|
3121
|
+
return;
|
|
3122
|
+
}
|
|
3123
|
+
if (node.type !== "Rule" || insideSkippedAtrule) return;
|
|
3124
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3125
|
+
const ruleBytes = csstree.generate(node).length;
|
|
3126
|
+
const decls = countDeclarations(node.block);
|
|
3127
|
+
totalDeclarations += decls;
|
|
3128
|
+
totalRules += 1;
|
|
3129
|
+
const ruleAllClasses = /* @__PURE__ */ new Set();
|
|
3130
|
+
const ruleSubjectClasses = /* @__PURE__ */ new Set();
|
|
3131
|
+
let hasAnyClass = false;
|
|
3132
|
+
node.prelude.children.forEach((selectorNode) => {
|
|
3133
|
+
totalSelectors += 1;
|
|
3134
|
+
const selectorText = csstree.generate(selectorNode);
|
|
3135
|
+
const shape = analyzeSelector(selectorText);
|
|
3136
|
+
if (shape.allClasses.length === 0) return;
|
|
3137
|
+
hasAnyClass = true;
|
|
3138
|
+
for (const c of shape.allClasses) ruleAllClasses.add(c);
|
|
3139
|
+
for (const c of shape.subjectClasses) ruleSubjectClasses.add(c);
|
|
3140
|
+
const subjectSet = new Set(shape.subjectClasses);
|
|
3141
|
+
for (const cls of shape.allClasses) {
|
|
3142
|
+
const a = accumFor(cls);
|
|
3143
|
+
a.ruleCount += 1;
|
|
3144
|
+
a.declarations += decls;
|
|
3145
|
+
a.breakpoints.add(media ?? "");
|
|
3146
|
+
if (a.selectors.length < selectorSampleLimit && !a.selectors.includes(selectorText)) {
|
|
3147
|
+
a.selectors.push(selectorText);
|
|
3148
|
+
}
|
|
3149
|
+
if (subjectSet.size === 1 && subjectSet.has(cls)) a.ownsSimple = true;
|
|
3150
|
+
}
|
|
3151
|
+
});
|
|
3152
|
+
if (!hasAnyClass) {
|
|
3153
|
+
base.rules += 1;
|
|
3154
|
+
base.declarations += decls;
|
|
3155
|
+
base.approxBytes += ruleBytes;
|
|
3156
|
+
return;
|
|
3157
|
+
}
|
|
3158
|
+
rules.push({
|
|
3159
|
+
media,
|
|
3160
|
+
bytes: ruleBytes,
|
|
3161
|
+
declarations: decls,
|
|
3162
|
+
classes: [...ruleAllClasses],
|
|
3163
|
+
subjectClasses: [...ruleSubjectClasses]
|
|
3164
|
+
});
|
|
3165
|
+
});
|
|
3166
|
+
};
|
|
3167
|
+
visit(ast, null, false);
|
|
3168
|
+
const prelim = /* @__PURE__ */ new Map();
|
|
3169
|
+
const sectionSizes = /* @__PURE__ */ new Map();
|
|
3170
|
+
for (const a of classMap.values()) {
|
|
3171
|
+
const pagesUsedIn = pageUseCount.get(a.name) ?? 0;
|
|
3172
|
+
let category;
|
|
3173
|
+
let bucket;
|
|
3174
|
+
if (/^w-/.test(a.name)) {
|
|
3175
|
+
category = "vendor";
|
|
3176
|
+
bucket = "vendor";
|
|
3177
|
+
} else if (webflowAutoPatterns.some((re) => re.test(a.name))) {
|
|
3178
|
+
category = "webflow-auto";
|
|
3179
|
+
bucket = "webflow-auto";
|
|
3180
|
+
} else if (utilityHints.some((re) => re.test(a.name)) || pagesUsedIn >= sharedMinPages) {
|
|
3181
|
+
category = "shared";
|
|
3182
|
+
bucket = "shared";
|
|
3183
|
+
} else {
|
|
3184
|
+
category = "section";
|
|
3185
|
+
bucket = sectionPrefix(a.name);
|
|
3186
|
+
sectionSizes.set(bucket, (sectionSizes.get(bucket) ?? 0) + 1);
|
|
3187
|
+
}
|
|
3188
|
+
prelim.set(a.name, { category, bucket, pagesUsedIn, accum: a });
|
|
3189
|
+
}
|
|
3190
|
+
const keptSections = /* @__PURE__ */ new Set();
|
|
3191
|
+
for (const [prefix, count] of sectionSizes) {
|
|
3192
|
+
if (count >= minSectionClasses) keptSections.add(prefix);
|
|
3193
|
+
}
|
|
3194
|
+
const sectionRemap = /* @__PURE__ */ new Map();
|
|
3195
|
+
for (const [prefix, count] of sectionSizes) {
|
|
3196
|
+
if (keptSections.has(prefix) || count === 0) continue;
|
|
3197
|
+
const near = mergeTypos && prefix.length >= 4 ? nearestKeptPrefix(prefix, keptSections) : null;
|
|
3198
|
+
sectionRemap.set(prefix, near ? { bucket: near, reason: "typo" } : { bucket: miscBucketName, reason: "small" });
|
|
3199
|
+
}
|
|
3200
|
+
const foldedAgg = /* @__PURE__ */ new Map();
|
|
3201
|
+
const classified = /* @__PURE__ */ new Map();
|
|
3202
|
+
const classes = [];
|
|
3203
|
+
for (const [name, p] of prelim) {
|
|
3204
|
+
let bucket = p.bucket;
|
|
3205
|
+
if (p.category === "section") {
|
|
3206
|
+
const remap = sectionRemap.get(p.bucket);
|
|
3207
|
+
if (remap) {
|
|
3208
|
+
bucket = remap.bucket;
|
|
3209
|
+
const key = `${p.bucket}\u2192${remap.bucket}`;
|
|
3210
|
+
const agg = foldedAgg.get(key) ?? { from: p.bucket, into: remap.bucket, reason: remap.reason, classes: 0 };
|
|
3211
|
+
agg.classes += 1;
|
|
3212
|
+
foldedAgg.set(key, agg);
|
|
3213
|
+
}
|
|
3214
|
+
}
|
|
3215
|
+
classified.set(name, { category: p.category, bucket });
|
|
3216
|
+
classes.push({
|
|
3217
|
+
name,
|
|
3218
|
+
category: p.category,
|
|
3219
|
+
proposedBucket: bucket,
|
|
3220
|
+
ruleCount: p.accum.ruleCount,
|
|
3221
|
+
declarations: p.accum.declarations,
|
|
3222
|
+
breakpoints: [...p.accum.breakpoints].sort(),
|
|
3223
|
+
pagesUsedIn: p.pagesUsedIn,
|
|
3224
|
+
selectors: p.accum.selectors,
|
|
3225
|
+
compoundOnly: !p.accum.ownsSimple
|
|
3226
|
+
});
|
|
3227
|
+
}
|
|
3228
|
+
classes.sort((x, y) => y.declarations - x.declarations || x.name.localeCompare(y.name));
|
|
3229
|
+
const foldedSections = [...foldedAgg.values()].map(({ from, into, classes: c, reason }) => ({ from, into, classes: c, reason })).sort((a, b) => b.classes - a.classes || a.from.localeCompare(b.from));
|
|
3230
|
+
const vendor = emptyBucket();
|
|
3231
|
+
const webflowAuto = emptyBucket();
|
|
3232
|
+
const shared = emptyBucket();
|
|
3233
|
+
const sections = {};
|
|
3234
|
+
const crossBoundary = [];
|
|
3235
|
+
const bucketStat = (bucket) => {
|
|
3236
|
+
if (bucket === "vendor") return vendor;
|
|
3237
|
+
if (bucket === "webflow-auto") return webflowAuto;
|
|
3238
|
+
if (bucket === "shared") return shared;
|
|
3239
|
+
let stat = sections[bucket];
|
|
3240
|
+
if (!stat) {
|
|
3241
|
+
stat = emptyBucket();
|
|
3242
|
+
sections[bucket] = stat;
|
|
3243
|
+
}
|
|
3244
|
+
return stat;
|
|
3245
|
+
};
|
|
3246
|
+
for (const rule of rules) {
|
|
3247
|
+
const ownerPool = rule.subjectClasses.length > 0 ? rule.subjectClasses : rule.classes;
|
|
3248
|
+
let owner = "base";
|
|
3249
|
+
let bestRank = -1;
|
|
3250
|
+
const sectionBucketsTouched = /* @__PURE__ */ new Set();
|
|
3251
|
+
for (const cls of rule.classes) {
|
|
3252
|
+
const c = classified.get(cls);
|
|
3253
|
+
if (c?.category === "section" && c.bucket !== miscBucketName) sectionBucketsTouched.add(c.bucket);
|
|
3254
|
+
}
|
|
3255
|
+
for (const cls of ownerPool) {
|
|
3256
|
+
const c = classified.get(cls);
|
|
3257
|
+
if (!c) continue;
|
|
3258
|
+
const rank = c.category === "section" ? 4 : c.category === "shared" ? 3 : c.category === "webflow-auto" ? 2 : 1;
|
|
3259
|
+
if (rank > bestRank) {
|
|
3260
|
+
bestRank = rank;
|
|
3261
|
+
owner = c.bucket;
|
|
3262
|
+
}
|
|
3263
|
+
}
|
|
3264
|
+
const stat = bucketStat(owner);
|
|
3265
|
+
stat.rules += 1;
|
|
3266
|
+
stat.declarations += rule.declarations;
|
|
3267
|
+
stat.approxBytes += rule.bytes;
|
|
3268
|
+
if (sectionBucketsTouched.size >= 2 && crossBoundary.length < flagLimit) {
|
|
3269
|
+
crossBoundary.push({
|
|
3270
|
+
selector: rule.classes.map((c) => `.${c}`).join(""),
|
|
3271
|
+
media: rule.media,
|
|
3272
|
+
classes: rule.classes,
|
|
3273
|
+
buckets: [...sectionBucketsTouched].sort()
|
|
3274
|
+
});
|
|
3275
|
+
}
|
|
3276
|
+
}
|
|
3277
|
+
for (const info of classes) {
|
|
3278
|
+
bucketStat(info.proposedBucket).classes += 1;
|
|
3279
|
+
}
|
|
3280
|
+
const fuzzyMiddle = [];
|
|
3281
|
+
const deadClasses = [];
|
|
3282
|
+
for (const info of classes) {
|
|
3283
|
+
if (info.category === "section" && info.pagesUsedIn >= 2 && info.pagesUsedIn < sharedMinPages) {
|
|
3284
|
+
fuzzyMiddle.push(info.name);
|
|
3285
|
+
}
|
|
3286
|
+
if (info.category !== "vendor" && info.pagesUsedIn === 0) {
|
|
3287
|
+
deadClasses.push(info.name);
|
|
3288
|
+
}
|
|
3289
|
+
}
|
|
3290
|
+
const pageOnlyClasses = [];
|
|
3291
|
+
for (const cls of pageUseCount.keys()) {
|
|
3292
|
+
if (!classMap.has(cls)) pageOnlyClasses.push(cls);
|
|
3293
|
+
}
|
|
3294
|
+
pageOnlyClasses.sort();
|
|
3295
|
+
deadClasses.sort();
|
|
3296
|
+
return {
|
|
3297
|
+
source: {
|
|
3298
|
+
bytes: css.length,
|
|
3299
|
+
rules: totalRules,
|
|
3300
|
+
selectors: totalSelectors,
|
|
3301
|
+
declarations: totalDeclarations,
|
|
3302
|
+
pageCount,
|
|
3303
|
+
sharedMinPages
|
|
3304
|
+
},
|
|
3305
|
+
breakpoints,
|
|
3306
|
+
buckets: { base, vendor, webflowAuto, shared, sections },
|
|
3307
|
+
classes,
|
|
3308
|
+
flags: {
|
|
3309
|
+
crossBoundarySelectors: crossBoundary,
|
|
3310
|
+
fuzzyMiddle: fuzzyMiddle.slice(0, flagLimit),
|
|
3311
|
+
deadClasses: deadClasses.slice(0, flagLimit),
|
|
3312
|
+
pageOnlyClasses: pageOnlyClasses.slice(0, flagLimit),
|
|
3313
|
+
foldedSections
|
|
3314
|
+
}
|
|
3315
|
+
};
|
|
3316
|
+
}
|
|
3317
|
+
function collectClassesFromPageData(pageData) {
|
|
3318
|
+
const out = /* @__PURE__ */ new Set();
|
|
3319
|
+
const walk = (value) => {
|
|
3320
|
+
if (!value) return;
|
|
3321
|
+
if (Array.isArray(value)) {
|
|
3322
|
+
for (const item of value) walk(item);
|
|
3323
|
+
return;
|
|
3324
|
+
}
|
|
3325
|
+
if (typeof value === "object") {
|
|
3326
|
+
for (const [key, v] of Object.entries(value)) {
|
|
3327
|
+
if ((key === "class" || key === "className") && typeof v === "string") {
|
|
3328
|
+
for (const token of v.split(/\s+/)) {
|
|
3329
|
+
if (token) out.add(token);
|
|
3330
|
+
}
|
|
3331
|
+
} else {
|
|
3332
|
+
walk(v);
|
|
3333
|
+
}
|
|
3334
|
+
}
|
|
3335
|
+
}
|
|
3336
|
+
};
|
|
3337
|
+
walk(pageData);
|
|
3338
|
+
return [...out];
|
|
3339
|
+
}
|
|
3340
|
+
function renderAuditMarkdown(report) {
|
|
3341
|
+
const { source, buckets, breakpoints, flags } = report;
|
|
3342
|
+
const kb = (n) => `${(n / 1024).toFixed(1)} KB`;
|
|
3343
|
+
const lines = [];
|
|
3344
|
+
lines.push("# CSS audit");
|
|
3345
|
+
lines.push("");
|
|
3346
|
+
lines.push(
|
|
3347
|
+
`**${kb(source.bytes)}** \xB7 ${source.rules} rules \xB7 ${source.selectors} selectors \xB7 ${source.declarations} declarations \xB7 ${source.pageCount} pages \xB7 shared threshold = ${source.sharedMinPages} pages`
|
|
3348
|
+
);
|
|
3349
|
+
lines.push("");
|
|
3350
|
+
lines.push(`Breakpoints: ${breakpoints.length ? breakpoints.map((b) => `\`${b}\``).join(", ") : "(none)"}`);
|
|
3351
|
+
lines.push("");
|
|
3352
|
+
lines.push("## Proposed buckets");
|
|
3353
|
+
lines.push("");
|
|
3354
|
+
lines.push("| bucket | classes | rules | declarations | ~size |");
|
|
3355
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
3356
|
+
const row = (name, b) => `| ${name} | ${b.classes} | ${b.rules} | ${b.declarations} | ${kb(b.approxBytes)} |`;
|
|
3357
|
+
lines.push(row("base (reset/typography)", buckets.base));
|
|
3358
|
+
lines.push(row("vendor (.w-*)", buckets.vendor));
|
|
3359
|
+
lines.push(row("webflow-auto (div-block/text-block/\u2026)", buckets.webflowAuto));
|
|
3360
|
+
lines.push(row("shared", buckets.shared));
|
|
3361
|
+
const sectionEntries = Object.entries(buckets.sections).sort((a, b) => b[1].approxBytes - a[1].approxBytes);
|
|
3362
|
+
for (const [prefix, stat] of sectionEntries) {
|
|
3363
|
+
if (prefix === "misc") continue;
|
|
3364
|
+
lines.push(row(`section: ${prefix}`, stat));
|
|
3365
|
+
}
|
|
3366
|
+
const misc = buckets.sections.misc;
|
|
3367
|
+
if (misc) lines.push(row("misc (folded small/orphan)", misc));
|
|
3368
|
+
lines.push("");
|
|
3369
|
+
lines.push(`_${sectionEntries.filter(([p]) => p !== "misc").length} real sections + ${misc ? "misc" : "no misc"}_`);
|
|
3370
|
+
lines.push("");
|
|
3371
|
+
lines.push("## Flags");
|
|
3372
|
+
lines.push("");
|
|
3373
|
+
lines.push(`- Cross-boundary selectors (touch \u22652 sections): **${flags.crossBoundarySelectors.length}**`);
|
|
3374
|
+
lines.push(`- Fuzzy-middle classes (near shared threshold): **${flags.fuzzyMiddle.length}**`);
|
|
3375
|
+
lines.push(`- Dead classes (defined, unused on any page): **${flags.deadClasses.length}**`);
|
|
3376
|
+
lines.push(`- Page-only classes (used in pages, not in this sheet): **${flags.pageOnlyClasses.length}**`);
|
|
3377
|
+
const foldedClasses = flags.foldedSections.reduce((n, f) => n + f.classes, 0);
|
|
3378
|
+
lines.push(
|
|
3379
|
+
`- Folded section buckets (small/typo \u2192 misc/kept): **${flags.foldedSections.length}** (${foldedClasses} classes)`
|
|
3380
|
+
);
|
|
3381
|
+
const typos = flags.foldedSections.filter((f) => f.reason === "typo");
|
|
3382
|
+
if (typos.length) {
|
|
3383
|
+
lines.push("");
|
|
3384
|
+
lines.push("Typo merges:");
|
|
3385
|
+
for (const f of typos) lines.push(`- \`${f.from}\` \u2192 \`${f.into}\` (${f.classes})`);
|
|
3386
|
+
}
|
|
3387
|
+
if (flags.crossBoundarySelectors.length) {
|
|
3388
|
+
lines.push("");
|
|
3389
|
+
lines.push("Sample cross-boundary selectors:");
|
|
3390
|
+
for (const f of flags.crossBoundarySelectors.slice(0, 10)) {
|
|
3391
|
+
lines.push(`- \`${f.selector}\`${f.media ? ` @ ${f.media}` : ""} \u2192 ${f.buckets.join(" + ")}`);
|
|
3392
|
+
}
|
|
3393
|
+
}
|
|
3394
|
+
return lines.join("\n");
|
|
3395
|
+
}
|
|
3396
|
+
|
|
3397
|
+
// lib/server/routes/api/cssAudit.ts
|
|
3398
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".meno", "rendered-websites"]);
|
|
3399
|
+
function findMirrorCss(root, maxDepth = 6) {
|
|
3400
|
+
const found = [];
|
|
3401
|
+
const walk = (dir, depth) => {
|
|
3402
|
+
if (depth > maxDepth) return;
|
|
3403
|
+
let entries;
|
|
3404
|
+
try {
|
|
3405
|
+
entries = readdirSync2(dir);
|
|
3406
|
+
} catch {
|
|
3407
|
+
return;
|
|
3408
|
+
}
|
|
3409
|
+
for (const entry of entries) {
|
|
3410
|
+
const full = path.join(dir, entry);
|
|
3411
|
+
let isDir = false;
|
|
3412
|
+
try {
|
|
3413
|
+
isDir = statSync(full).isDirectory();
|
|
3414
|
+
} catch {
|
|
3415
|
+
continue;
|
|
3416
|
+
}
|
|
3417
|
+
if (isDir) {
|
|
3418
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
3419
|
+
walk(full, depth + 1);
|
|
3420
|
+
} else if (entry.endsWith(".css") && full.replace(/\\/g, "/").includes("/_mirror/css/")) {
|
|
3421
|
+
found.push(full);
|
|
3422
|
+
}
|
|
3423
|
+
}
|
|
3424
|
+
};
|
|
3425
|
+
walk(root, 0);
|
|
3426
|
+
return found.sort();
|
|
3427
|
+
}
|
|
3428
|
+
function resolveCssParam(root, rel) {
|
|
3429
|
+
const resolved = path.resolve(root, rel);
|
|
3430
|
+
const normalizedRoot = path.resolve(root);
|
|
3431
|
+
if (resolved !== normalizedRoot && !resolved.startsWith(normalizedRoot + path.sep)) return null;
|
|
3432
|
+
return existsSync5(resolved) ? resolved : null;
|
|
3433
|
+
}
|
|
3434
|
+
function handleCssAuditRoute(url, pageService) {
|
|
3435
|
+
const root = getProjectRoot();
|
|
3436
|
+
const cssParam = url.searchParams.get("css");
|
|
3437
|
+
let cssFiles;
|
|
3438
|
+
if (cssParam) {
|
|
3439
|
+
const resolved = resolveCssParam(root, cssParam);
|
|
3440
|
+
if (!resolved) {
|
|
3441
|
+
return jsonResponse({ error: `Stylesheet not found or outside project: ${cssParam}` }, { status: 404 });
|
|
3442
|
+
}
|
|
3443
|
+
cssFiles = [resolved];
|
|
3444
|
+
} else {
|
|
3445
|
+
cssFiles = findMirrorCss(root);
|
|
3446
|
+
if (cssFiles.length === 0) {
|
|
3447
|
+
return jsonResponse(
|
|
3448
|
+
{
|
|
3449
|
+
error: "No mirror stylesheet found",
|
|
3450
|
+
hint: "Expected a Webflow export at public/_mirror/css/*.css. Pass ?css=<relative path> to audit a specific stylesheet."
|
|
3451
|
+
},
|
|
3452
|
+
{ status: 404 }
|
|
3453
|
+
);
|
|
3454
|
+
}
|
|
3455
|
+
}
|
|
3456
|
+
const css = cssFiles.map((f) => readFileSync2(f, "utf8")).join("\n");
|
|
3457
|
+
const pages = [];
|
|
3458
|
+
for (const pagePath of pageService.getAllPagePaths()) {
|
|
3459
|
+
const data = pageService.getPageData(pagePath);
|
|
3460
|
+
if (!data) continue;
|
|
3461
|
+
pages.push({ path: pagePath, classes: collectClassesFromPageData(data) });
|
|
3462
|
+
}
|
|
3463
|
+
const intParam = (name) => {
|
|
3464
|
+
const raw = url.searchParams.get(name);
|
|
3465
|
+
if (!raw) return void 0;
|
|
3466
|
+
const n = Number.parseInt(raw, 10);
|
|
3467
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3468
|
+
};
|
|
3469
|
+
const options = {};
|
|
3470
|
+
const sharedMinPages = intParam("sharedMinPages");
|
|
3471
|
+
const minSectionClasses = intParam("minSectionClasses");
|
|
3472
|
+
if (sharedMinPages !== void 0) options.sharedMinPages = sharedMinPages;
|
|
3473
|
+
if (minSectionClasses !== void 0) options.minSectionClasses = minSectionClasses;
|
|
3474
|
+
const report = auditStylesheet({
|
|
3475
|
+
css,
|
|
3476
|
+
pages,
|
|
3477
|
+
options: Object.keys(options).length > 0 ? options : void 0
|
|
3478
|
+
});
|
|
3479
|
+
const cssFilesRel = cssFiles.map((f) => path.relative(root, f));
|
|
3480
|
+
const format = url.searchParams.get("format");
|
|
3481
|
+
if (format === "md" || format === "markdown") {
|
|
3482
|
+
const md = `${renderAuditMarkdown(report)}
|
|
3483
|
+
|
|
3484
|
+
_Audited: ${cssFilesRel.join(", ")}_
|
|
3485
|
+
`;
|
|
3486
|
+
return new Response(md, {
|
|
3487
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8", "Cache-Control": "no-store, max-age=0" }
|
|
3488
|
+
});
|
|
3489
|
+
}
|
|
3490
|
+
return jsonResponse({ ...report, cssFiles: cssFilesRel });
|
|
3491
|
+
}
|
|
3492
|
+
|
|
3493
|
+
// lib/server/routes/api/deMirror.ts
|
|
3494
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3495
|
+
import path2 from "node:path";
|
|
3496
|
+
|
|
3497
|
+
// lib/server/deMirror.ts
|
|
3498
|
+
import * as csstree2 from "css-tree";
|
|
3499
|
+
var CLASS_TOKEN_RE2 = /\.(-?[_a-zA-Z][\w-]*)/g;
|
|
3500
|
+
var FUNCTIONAL_PSEUDO_RE2 = /:(not|is|where|has|matches)\([^()]*\)/gi;
|
|
3501
|
+
function classTokens2(selector) {
|
|
3502
|
+
const out = [];
|
|
3503
|
+
let m;
|
|
3504
|
+
CLASS_TOKEN_RE2.lastIndex = 0;
|
|
3505
|
+
while ((m = CLASS_TOKEN_RE2.exec(selector)) !== null) {
|
|
3506
|
+
if (m[1]) out.push(m[1]);
|
|
3507
|
+
}
|
|
3508
|
+
return out;
|
|
3509
|
+
}
|
|
3510
|
+
function subjectClassesOf(selector) {
|
|
3511
|
+
const cleaned = selector.replace(FUNCTIONAL_PSEUDO_RE2, " ");
|
|
3512
|
+
const segments = cleaned.split(/\s*[>+~]\s*|\s+/).filter((s) => s.trim().length > 0);
|
|
3513
|
+
const subject = segments[segments.length - 1] ?? cleaned;
|
|
3514
|
+
return classTokens2(subject);
|
|
3515
|
+
}
|
|
3516
|
+
function declBlock(block) {
|
|
3517
|
+
const lines = [];
|
|
3518
|
+
if (block && block.type === "Block") {
|
|
3519
|
+
block.children.forEach((child) => {
|
|
3520
|
+
if (child.type !== "Declaration") return;
|
|
3521
|
+
const g = csstree2.generate(child);
|
|
3522
|
+
const i = g.indexOf(":");
|
|
3523
|
+
lines.push(` ${i > 0 ? `${g.slice(0, i)}: ${g.slice(i + 1)}` : g};`);
|
|
3524
|
+
});
|
|
3525
|
+
}
|
|
3526
|
+
return { text: lines.join("\n"), count: lines.length };
|
|
3527
|
+
}
|
|
3528
|
+
function captureClassRules(css) {
|
|
3529
|
+
const ast = csstree2.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3530
|
+
const out = [];
|
|
3531
|
+
const visit = (container, media, skip) => {
|
|
3532
|
+
const children = "children" in container ? container.children : null;
|
|
3533
|
+
if (!children) return;
|
|
3534
|
+
children.forEach((node) => {
|
|
3535
|
+
if (node.type === "Atrule") {
|
|
3536
|
+
const name = node.name.toLowerCase();
|
|
3537
|
+
if (name === "media") {
|
|
3538
|
+
const prelude = node.prelude ? csstree2.generate(node.prelude) : "";
|
|
3539
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3540
|
+
} else if (name === "supports") {
|
|
3541
|
+
if (node.block) visit(node.block, media, false);
|
|
3542
|
+
} else if (node.block) {
|
|
3543
|
+
visit(node.block, media, true);
|
|
3544
|
+
}
|
|
3545
|
+
return;
|
|
3546
|
+
}
|
|
3547
|
+
if (node.type !== "Rule" || skip) return;
|
|
3548
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3549
|
+
const selectors = [];
|
|
3550
|
+
const subject = /* @__PURE__ */ new Set();
|
|
3551
|
+
const all = /* @__PURE__ */ new Set();
|
|
3552
|
+
node.prelude.children.forEach((sel) => {
|
|
3553
|
+
const text = csstree2.generate(sel);
|
|
3554
|
+
const cls = classTokens2(text);
|
|
3555
|
+
if (cls.length === 0) return;
|
|
3556
|
+
selectors.push(text);
|
|
3557
|
+
for (const c of cls) all.add(c);
|
|
3558
|
+
for (const c of subjectClassesOf(text)) subject.add(c);
|
|
3559
|
+
});
|
|
3560
|
+
if (selectors.length === 0) return;
|
|
3561
|
+
const { text: decls, count } = declBlock(node.block);
|
|
3562
|
+
out.push({
|
|
3563
|
+
selector: selectors.join(", "),
|
|
3564
|
+
media,
|
|
3565
|
+
subjectClasses: [...subject],
|
|
3566
|
+
allClasses: [...all],
|
|
3567
|
+
declarations: count,
|
|
3568
|
+
bytes: csstree2.generate(node).length,
|
|
3569
|
+
text: `${selectors.join(",\n")} {
|
|
3570
|
+
${decls}
|
|
3571
|
+
}`
|
|
3572
|
+
});
|
|
3573
|
+
});
|
|
3574
|
+
};
|
|
3575
|
+
visit(ast, null, false);
|
|
3576
|
+
return out;
|
|
3577
|
+
}
|
|
3578
|
+
function assembleSlice(rules) {
|
|
3579
|
+
const base = rules.filter((r) => r.media === null);
|
|
3580
|
+
const byMedia = /* @__PURE__ */ new Map();
|
|
3581
|
+
for (const r of rules) {
|
|
3582
|
+
if (r.media === null) continue;
|
|
3583
|
+
const list = byMedia.get(r.media) ?? [];
|
|
3584
|
+
list.push(r);
|
|
3585
|
+
byMedia.set(r.media, list);
|
|
3586
|
+
}
|
|
3587
|
+
const parts = [];
|
|
3588
|
+
for (const r of base) parts.push(r.text);
|
|
3589
|
+
for (const [media, list] of byMedia) {
|
|
3590
|
+
const inner = list.map((r) => r.text.replace(/^/gm, " ")).join("\n");
|
|
3591
|
+
parts.push(`@media ${media} {
|
|
3592
|
+
${inner}
|
|
3593
|
+
}`);
|
|
3594
|
+
}
|
|
3595
|
+
return parts.join("\n\n");
|
|
3596
|
+
}
|
|
3597
|
+
function planDeMirror(input) {
|
|
3598
|
+
const { css, components, options = {} } = input;
|
|
3599
|
+
const warningSampleLimit = options.warningSampleLimit ?? 40;
|
|
3600
|
+
const compClasses = components.map((c) => ({ name: c.name, set: new Set(c.classes) }));
|
|
3601
|
+
const rules = captureClassRules(css);
|
|
3602
|
+
const perComponent = /* @__PURE__ */ new Map();
|
|
3603
|
+
const sharedSelectors = [];
|
|
3604
|
+
const deadSelectors = [];
|
|
3605
|
+
let movedRules = 0;
|
|
3606
|
+
let movedBytes = 0;
|
|
3607
|
+
let sharedRules = 0;
|
|
3608
|
+
let deadRules = 0;
|
|
3609
|
+
let sharedBytes = 0;
|
|
3610
|
+
let deadBytes = 0;
|
|
3611
|
+
let totalBytes = 0;
|
|
3612
|
+
for (const rule of rules) {
|
|
3613
|
+
totalBytes += rule.bytes;
|
|
3614
|
+
const need = rule.subjectClasses.length > 0 ? rule.subjectClasses : rule.allClasses;
|
|
3615
|
+
const owners = [];
|
|
3616
|
+
for (const c of compClasses) {
|
|
3617
|
+
if (need.every((cls) => c.set.has(cls))) owners.push(c.name);
|
|
3618
|
+
}
|
|
3619
|
+
if (owners.length === 1) {
|
|
3620
|
+
const name = owners[0];
|
|
3621
|
+
const list = perComponent.get(name) ?? [];
|
|
3622
|
+
list.push(rule);
|
|
3623
|
+
perComponent.set(name, list);
|
|
3624
|
+
movedRules += 1;
|
|
3625
|
+
movedBytes += rule.bytes;
|
|
3626
|
+
} else if (owners.length === 0) {
|
|
3627
|
+
deadRules += 1;
|
|
3628
|
+
deadBytes += rule.bytes;
|
|
3629
|
+
if (deadSelectors.length < warningSampleLimit) deadSelectors.push(rule.selector);
|
|
3630
|
+
} else {
|
|
3631
|
+
sharedRules += 1;
|
|
3632
|
+
sharedBytes += rule.bytes;
|
|
3633
|
+
if (sharedSelectors.length < warningSampleLimit) sharedSelectors.push(rule.selector);
|
|
3634
|
+
}
|
|
3635
|
+
}
|
|
3636
|
+
const componentsOut = [];
|
|
3637
|
+
for (const [name, list] of perComponent) {
|
|
3638
|
+
const breakpoints = [...new Set(list.map((r) => r.media ?? ""))].sort();
|
|
3639
|
+
componentsOut.push({
|
|
3640
|
+
name,
|
|
3641
|
+
ruleCount: list.length,
|
|
3642
|
+
declarations: list.reduce((n, r) => n + r.declarations, 0),
|
|
3643
|
+
approxBytes: list.reduce((n, r) => n + r.bytes, 0),
|
|
3644
|
+
breakpoints,
|
|
3645
|
+
css: assembleSlice(list)
|
|
3646
|
+
});
|
|
3647
|
+
}
|
|
3648
|
+
componentsOut.sort((a, b) => b.approxBytes - a.approxBytes || a.name.localeCompare(b.name));
|
|
3649
|
+
const roundTrip = movedRules + sharedRules + deadRules === rules.length;
|
|
3650
|
+
return {
|
|
3651
|
+
source: { classRules: rules.length, classRuleBytes: totalBytes, componentCount: components.length },
|
|
3652
|
+
components: componentsOut,
|
|
3653
|
+
global: { sharedRules, deadRules, approxBytes: sharedBytes + deadBytes },
|
|
3654
|
+
moved: { rules: movedRules, approxBytes: movedBytes, components: componentsOut.length },
|
|
3655
|
+
warnings: { sharedSelectors, deadSelectors },
|
|
3656
|
+
roundTrip
|
|
3657
|
+
};
|
|
3658
|
+
}
|
|
3659
|
+
function renderDeMirrorMarkdown(plan) {
|
|
3660
|
+
const kb = (n) => `${(n / 1024).toFixed(1)} KB`;
|
|
3661
|
+
const lines = [];
|
|
3662
|
+
lines.push("# De-mirror plan");
|
|
3663
|
+
lines.push("");
|
|
3664
|
+
lines.push(
|
|
3665
|
+
`${plan.source.classRules} class rules (${kb(plan.source.classRuleBytes)}) across ${plan.source.componentCount} components \xB7 round-trip ${plan.roundTrip ? "\u2705 lossless" : "\u274C FAILED"}`
|
|
3666
|
+
);
|
|
3667
|
+
lines.push("");
|
|
3668
|
+
lines.push(
|
|
3669
|
+
`**Moves onto components:** ${plan.moved.rules} rules \u2192 ${plan.moved.components} components (${kb(plan.moved.approxBytes)})`
|
|
3670
|
+
);
|
|
3671
|
+
lines.push(
|
|
3672
|
+
`**Stays global:** ${plan.global.sharedRules} shared + ${plan.global.deadRules} dead = ${kb(plan.global.approxBytes)}`
|
|
3673
|
+
);
|
|
3674
|
+
lines.push("");
|
|
3675
|
+
lines.push("## Slices (largest first)");
|
|
3676
|
+
lines.push("");
|
|
3677
|
+
lines.push("| component | rules | declarations | ~size | breakpoints |");
|
|
3678
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
3679
|
+
for (const c of plan.components.slice(0, 40)) {
|
|
3680
|
+
lines.push(`| ${c.name} | ${c.ruleCount} | ${c.declarations} | ${kb(c.approxBytes)} | ${c.breakpoints.length} |`);
|
|
3681
|
+
}
|
|
3682
|
+
if (plan.components.length > 40) lines.push(`| \u2026 +${plan.components.length - 40} more | | | | |`);
|
|
3683
|
+
lines.push("");
|
|
3684
|
+
lines.push(
|
|
3685
|
+
`_Shared (kept global): ${plan.warnings.sharedSelectors.slice(0, 6).join(", ") || "none"}${plan.warnings.sharedSelectors.length > 6 ? " \u2026" : ""}_`
|
|
3686
|
+
);
|
|
3687
|
+
return lines.join("\n");
|
|
3688
|
+
}
|
|
3689
|
+
|
|
3690
|
+
// lib/server/routes/api/deMirror.ts
|
|
3691
|
+
function componentsUsedByPage(pageData, allNames) {
|
|
3692
|
+
const hay = JSON.stringify(pageData ?? "");
|
|
3693
|
+
const used = /* @__PURE__ */ new Set();
|
|
3694
|
+
for (const name of allNames) {
|
|
3695
|
+
if (hay.includes(`"${name}"`)) used.add(name);
|
|
3696
|
+
}
|
|
3697
|
+
return used;
|
|
3698
|
+
}
|
|
3699
|
+
function handleDeMirrorRoute(url, pageService, componentService) {
|
|
3700
|
+
const root = getProjectRoot();
|
|
3701
|
+
const cssParam = url.searchParams.get("css");
|
|
3702
|
+
let cssFiles;
|
|
3703
|
+
if (cssParam) {
|
|
3704
|
+
const resolved = resolveCssParam(root, cssParam);
|
|
3705
|
+
if (!resolved) {
|
|
3706
|
+
return jsonResponse({ error: `Stylesheet not found or outside project: ${cssParam}` }, { status: 404 });
|
|
3707
|
+
}
|
|
3708
|
+
cssFiles = [resolved];
|
|
3709
|
+
} else {
|
|
3710
|
+
cssFiles = findMirrorCss(root);
|
|
3711
|
+
if (cssFiles.length === 0) {
|
|
3712
|
+
return jsonResponse(
|
|
3713
|
+
{
|
|
3714
|
+
error: "No mirror stylesheet found",
|
|
3715
|
+
hint: "Expected a Webflow export at public/_mirror/css/*.css. Pass ?css=<relative path> to target a specific stylesheet."
|
|
3716
|
+
},
|
|
3717
|
+
{ status: 404 }
|
|
3718
|
+
);
|
|
3719
|
+
}
|
|
3720
|
+
}
|
|
3721
|
+
const css = cssFiles.map((f) => readFileSync3(f, "utf8")).join("\n");
|
|
3722
|
+
const all = componentService.getAllComponents();
|
|
3723
|
+
const components = Object.entries(all).map(([name, def]) => ({
|
|
3724
|
+
name,
|
|
3725
|
+
classes: collectClassesFromPageData(def)
|
|
3726
|
+
}));
|
|
3727
|
+
if (components.length === 0) {
|
|
3728
|
+
return jsonResponse(
|
|
3729
|
+
{
|
|
3730
|
+
error: "No components found",
|
|
3731
|
+
hint: "Componentize the project first (e.g. /componentize-mirror), then de-mirror."
|
|
3732
|
+
},
|
|
3733
|
+
{ status: 422 }
|
|
3734
|
+
);
|
|
3735
|
+
}
|
|
3736
|
+
const plan = planDeMirror({ css, components });
|
|
3737
|
+
const slug = url.searchParams.get("slug");
|
|
3738
|
+
let scoped = plan.components;
|
|
3739
|
+
let scopedNote;
|
|
3740
|
+
if (slug) {
|
|
3741
|
+
const pagePath = slug === "index" ? "/" : `/${slug.replace(/^\//, "")}`;
|
|
3742
|
+
const pageData = pageService.getPageData(pagePath);
|
|
3743
|
+
if (!pageData) {
|
|
3744
|
+
return jsonResponse({ error: `Page not found: ${slug}` }, { status: 404 });
|
|
3745
|
+
}
|
|
3746
|
+
const used = componentsUsedByPage(
|
|
3747
|
+
pageData,
|
|
3748
|
+
components.map((c) => c.name)
|
|
3749
|
+
);
|
|
3750
|
+
scoped = plan.components.filter((c) => used.has(c.name));
|
|
3751
|
+
scopedNote = `filtered to ${scoped.length} component(s) used by /${slug} (ownership computed project-wide)`;
|
|
3752
|
+
}
|
|
3753
|
+
const cssFilesRel = cssFiles.map((f) => path2.relative(root, f));
|
|
3754
|
+
const view = { ...plan, components: scoped };
|
|
3755
|
+
const format = url.searchParams.get("format");
|
|
3756
|
+
if (format === "md" || format === "markdown") {
|
|
3757
|
+
const md = `${renderDeMirrorMarkdown(view)}
|
|
3758
|
+
${scopedNote ? `
|
|
3759
|
+
_${scopedNote}_
|
|
3760
|
+
` : ""}
|
|
3761
|
+
_Sheet: ${cssFilesRel.join(", ")}_
|
|
3762
|
+
`;
|
|
3763
|
+
return new Response(md, {
|
|
3764
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8", "Cache-Control": "no-store, max-age=0" }
|
|
3765
|
+
});
|
|
3766
|
+
}
|
|
3767
|
+
const includeSlices = url.searchParams.get("slices") === "true";
|
|
3768
|
+
const responseComponents = includeSlices ? scoped : scoped.map(({ css: _css, ...meta }) => meta);
|
|
3769
|
+
return jsonResponse({
|
|
3770
|
+
...view,
|
|
3771
|
+
components: responseComponents,
|
|
3772
|
+
cssFiles: cssFilesRel,
|
|
3773
|
+
...scopedNote ? { scopedNote } : {},
|
|
3774
|
+
dryRun: true
|
|
3775
|
+
});
|
|
3776
|
+
}
|
|
3777
|
+
|
|
3778
|
+
// lib/server/routes/api/functions.ts
|
|
3779
|
+
import path3 from "node:path";
|
|
2885
3780
|
async function loadProjectEnv() {
|
|
2886
3781
|
const envPath = projectPaths.env();
|
|
2887
3782
|
const env = {};
|
|
@@ -2915,7 +3810,7 @@ async function handleFunctionsRoute(req, url) {
|
|
|
2915
3810
|
return void 0;
|
|
2916
3811
|
}
|
|
2917
3812
|
const functionPath = url.pathname.replace("/api/", "");
|
|
2918
|
-
const functionFilePath =
|
|
3813
|
+
const functionFilePath = path3.join(projectPaths.functions(), "api", `${functionPath}.ts`);
|
|
2919
3814
|
if (!await fileExists(functionFilePath)) {
|
|
2920
3815
|
const jsFilePath = functionFilePath.replace(".ts", ".js");
|
|
2921
3816
|
if (!await fileExists(jsFilePath)) {
|
|
@@ -3072,6 +3967,18 @@ async function handleCoreApiRoutes(req, url, context) {
|
|
|
3072
3967
|
if (url.pathname === "/api/enums" && req.method === "GET") {
|
|
3073
3968
|
return await handleRouteError(() => handleEnumsRoute(), "Failed to fetch enums");
|
|
3074
3969
|
}
|
|
3970
|
+
if (url.pathname === "/api/css-audit" && req.method === "GET") {
|
|
3971
|
+
return await handleRouteError(
|
|
3972
|
+
() => Promise.resolve(handleCssAuditRoute(url, pageService)),
|
|
3973
|
+
"Failed to generate CSS audit"
|
|
3974
|
+
);
|
|
3975
|
+
}
|
|
3976
|
+
if (url.pathname === "/api/de-mirror" && req.method === "GET") {
|
|
3977
|
+
return await handleRouteError(
|
|
3978
|
+
() => Promise.resolve(handleDeMirrorRoute(url, pageService, componentService)),
|
|
3979
|
+
"Failed to generate de-mirror plan"
|
|
3980
|
+
);
|
|
3981
|
+
}
|
|
3075
3982
|
if (url.pathname === "/api/usage" && req.method === "GET") {
|
|
3076
3983
|
return await handleRouteError(() => {
|
|
3077
3984
|
const cmsCollections = cmsService ? cmsService.getAllSchemas().size : 0;
|
|
@@ -3647,21 +4554,21 @@ function processCMSPropsTemplate(props, cmsItem, locale, i18nConfig) {
|
|
|
3647
4554
|
}
|
|
3648
4555
|
|
|
3649
4556
|
// lib/server/ssr/imageMetadata.ts
|
|
3650
|
-
import * as
|
|
4557
|
+
import * as path4 from "node:path";
|
|
3651
4558
|
var RESPONSIVE_WIDTHS = [500, 800, 1080, 1600, 2400];
|
|
3652
4559
|
var DEFAULT_SIZES = "100vw";
|
|
3653
4560
|
async function buildImageMetadataMap() {
|
|
3654
4561
|
const metadataMap = /* @__PURE__ */ new Map();
|
|
3655
4562
|
try {
|
|
3656
4563
|
const imagesDir = projectPaths.images();
|
|
3657
|
-
const manifestPath =
|
|
4564
|
+
const manifestPath = path4.join(imagesDir, "manifest.json");
|
|
3658
4565
|
if (!await fileExists(manifestPath)) {
|
|
3659
4566
|
return metadataMap;
|
|
3660
4567
|
}
|
|
3661
4568
|
const manifestContent = await readTextFile(manifestPath);
|
|
3662
4569
|
const manifest = JSON.parse(manifestContent);
|
|
3663
4570
|
for (const [filename, entry] of Object.entries(manifest.images)) {
|
|
3664
|
-
const baseName =
|
|
4571
|
+
const baseName = path4.basename(filename, path4.extname(filename));
|
|
3665
4572
|
const webpSrcsetParts = [];
|
|
3666
4573
|
const avifSrcsetParts = [];
|
|
3667
4574
|
for (const variant of entry.variants) {
|
|
@@ -3839,8 +4746,8 @@ function resolveFilterValue(value, scope) {
|
|
|
3839
4746
|
if (typeof value !== "string" || !value.startsWith("{{") || !value.endsWith("}}")) {
|
|
3840
4747
|
return value;
|
|
3841
4748
|
}
|
|
3842
|
-
const
|
|
3843
|
-
const resolved = scope ? getNestedValue(scope,
|
|
4749
|
+
const path5 = value.slice(2, -2).trim();
|
|
4750
|
+
const resolved = scope ? getNestedValue(scope, path5) : void 0;
|
|
3844
4751
|
return resolved !== void 0 ? resolved : UNRESOLVED_FILTER_VALUE;
|
|
3845
4752
|
}
|
|
3846
4753
|
function resolveFilterTemplates(filter, scope, collection) {
|
|
@@ -4250,8 +5157,8 @@ function getPropItems(source, ctx) {
|
|
|
4250
5157
|
if (source.startsWith("{{") && source.endsWith("}}")) {
|
|
4251
5158
|
const templateCtx = ctx.templateContext;
|
|
4252
5159
|
if (templateCtx) {
|
|
4253
|
-
const
|
|
4254
|
-
const resolved = getNestedValue(templateCtx,
|
|
5160
|
+
const path5 = source.slice(2, -2).trim();
|
|
5161
|
+
const resolved = getNestedValue(templateCtx, path5);
|
|
4255
5162
|
if (Array.isArray(resolved)) {
|
|
4256
5163
|
return resolved;
|
|
4257
5164
|
} else if (resolved !== void 0) {
|
|
@@ -4288,12 +5195,12 @@ function buildNodeElementClass(ctx, label, isSlotContent2) {
|
|
|
4288
5195
|
const effectiveFileType = useComponentContext ? "component" : "page";
|
|
4289
5196
|
const effectiveFileName = useComponentContext ? ctx.componentContext : pagePath ? pagePath.replace(/^\//, "").replace(/\//g, "_") || "index" : "page";
|
|
4290
5197
|
const rawPath = ctx.elementPath || [];
|
|
4291
|
-
const
|
|
5198
|
+
const path5 = useComponentContext && ctx.componentRootPath ? rawPath.slice(ctx.componentRootPath.length) : rawPath;
|
|
4292
5199
|
const elementClassCtx = {
|
|
4293
5200
|
fileType: effectiveFileType,
|
|
4294
5201
|
fileName: effectiveFileName || "page",
|
|
4295
5202
|
label,
|
|
4296
|
-
path:
|
|
5203
|
+
path: path5
|
|
4297
5204
|
};
|
|
4298
5205
|
return generateElementClassName(elementClassCtx);
|
|
4299
5206
|
}
|
|
@@ -5454,6 +6361,7 @@ function generateAllInlineDataScripts(collections) {
|
|
|
5454
6361
|
}
|
|
5455
6362
|
|
|
5456
6363
|
// lib/server/ssr/htmlGenerator.ts
|
|
6364
|
+
import { posix } from "node:path";
|
|
5457
6365
|
function generateImagePreloadTags(preloadImages) {
|
|
5458
6366
|
if (preloadImages.length === 0) return "";
|
|
5459
6367
|
return preloadImages.map(
|
|
@@ -5464,6 +6372,43 @@ function minifyCSS(code) {
|
|
|
5464
6372
|
if (!code.trim()) return code;
|
|
5465
6373
|
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{};:,>~])\s*/g, "$1").replace(/\s+/g, " ").replace(/\{\s+/g, "{").replace(/\s+\}/g, "}").replace(/;}/g, "}").trim();
|
|
5466
6374
|
}
|
|
6375
|
+
async function collectPageCssImports(frontmatter, pagePath, nonceAttr) {
|
|
6376
|
+
if (!frontmatter?.includes(".css")) return "";
|
|
6377
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6378
|
+
const specs = [];
|
|
6379
|
+
const re = /import\b[^'"]*['"]([^'"]+\.css)(?:\?[^'"]*)?['"]/g;
|
|
6380
|
+
let m;
|
|
6381
|
+
while ((m = re.exec(frontmatter)) !== null) {
|
|
6382
|
+
const spec = m[1];
|
|
6383
|
+
if (!spec || seen.has(spec)) continue;
|
|
6384
|
+
seen.add(spec);
|
|
6385
|
+
if (spec.endsWith("styles/theme.css")) continue;
|
|
6386
|
+
specs.push(spec);
|
|
6387
|
+
}
|
|
6388
|
+
if (specs.length === 0) return "";
|
|
6389
|
+
const pageDir = posix.join("src/pages", posix.dirname(pagePath || "/"));
|
|
6390
|
+
const tags = [];
|
|
6391
|
+
for (const spec of specs) {
|
|
6392
|
+
if (/^(?:https?:)?\/\//.test(spec)) {
|
|
6393
|
+
tags.push(`<link rel="stylesheet" href="${escapeHtml(spec)}">`);
|
|
6394
|
+
continue;
|
|
6395
|
+
}
|
|
6396
|
+
const candidates = spec.startsWith("/") ? [spec.slice(1)] : [posix.normalize(posix.join(pageDir, spec)), `src/${spec.replace(/^(?:\.\.?\/)+/, "")}`];
|
|
6397
|
+
let css = null;
|
|
6398
|
+
for (const rel of candidates) {
|
|
6399
|
+
if (rel.startsWith("..")) continue;
|
|
6400
|
+
try {
|
|
6401
|
+
css = await readTextFile(resolveProjectPath(rel));
|
|
6402
|
+
break;
|
|
6403
|
+
} catch {
|
|
6404
|
+
}
|
|
6405
|
+
}
|
|
6406
|
+
if (css == null) continue;
|
|
6407
|
+
const content = rewriteViewportUnitsInStylesheet(css).replace(/<\/style/gi, "<\\/style");
|
|
6408
|
+
tags.push(`<style${nonceAttr} data-meno-page-css="${escapeHtml(spec)}">${content}</style>`);
|
|
6409
|
+
}
|
|
6410
|
+
return tags.join("\n ");
|
|
6411
|
+
}
|
|
5467
6412
|
async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePath = "/", baseUrl = "", useBuiltBundle = false, locale, slugMappings, cmsContext, cmsService, externalScriptPath) {
|
|
5468
6413
|
let options;
|
|
5469
6414
|
if ("pageData" in pageDataOrOptions) {
|
|
@@ -5485,7 +6430,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5485
6430
|
const {
|
|
5486
6431
|
pageData,
|
|
5487
6432
|
globalComponents: components = {},
|
|
5488
|
-
pagePath:
|
|
6433
|
+
pagePath: path5 = "/",
|
|
5489
6434
|
baseUrl: base = "",
|
|
5490
6435
|
useBuiltBundle: useBundled = false,
|
|
5491
6436
|
locale: loc,
|
|
@@ -5509,7 +6454,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5509
6454
|
const rendered = await renderPageSSR(
|
|
5510
6455
|
pageData,
|
|
5511
6456
|
components,
|
|
5512
|
-
|
|
6457
|
+
path5,
|
|
5513
6458
|
base,
|
|
5514
6459
|
loc,
|
|
5515
6460
|
void 0,
|
|
@@ -5696,7 +6641,7 @@ picture {
|
|
|
5696
6641
|
line-height: normal;
|
|
5697
6642
|
}`;
|
|
5698
6643
|
const combinedCSS = [fontCSS, themeColorVariablesCSS, variablesCSS, baseCSS, componentCSS, utilityCSS, interactiveCSS].filter(Boolean).join("\n");
|
|
5699
|
-
const cssWithStableViewport =
|
|
6644
|
+
const cssWithStableViewport = rewriteViewportUnitsInStylesheet(combinedCSS);
|
|
5700
6645
|
const finalCSS = useBundled ? minifyCSS(cssWithStableViewport) : cssWithStableViewport;
|
|
5701
6646
|
const prefetchConfig = await loadPrefetchConfig();
|
|
5702
6647
|
const menoConfig = prefetchConfig.enabled ? { prefetch: prefetchConfig } : {};
|
|
@@ -5725,6 +6670,7 @@ ${externalJavaScript}`;
|
|
|
5725
6670
|
const styleContent = useBundled ? finalCSS : `
|
|
5726
6671
|
${combinedCSS.split("\n").join("\n ")}
|
|
5727
6672
|
`;
|
|
6673
|
+
const pageCssTags = await collectPageCssImports(pageData._frontmatter, pagePath, nonceAttr);
|
|
5728
6674
|
const htmlDocument = `<!DOCTYPE html>
|
|
5729
6675
|
<html lang="${rendered.locale}" theme="${themeConfig.default}">
|
|
5730
6676
|
<head>
|
|
@@ -5738,7 +6684,8 @@ ${externalJavaScript}`;
|
|
|
5738
6684
|
` : ""}${libraryTags.headCSS ? `${libraryTags.headCSS}
|
|
5739
6685
|
` : ""}${libraryTags.headJS ? `${libraryTags.headJS}
|
|
5740
6686
|
` : ""}${rendered.meta}
|
|
5741
|
-
${configInlineScript}${cmsInlineScript}${clientDataScripts}<style id="meno-styles"${nonceAttr}>${styleContent}</style>${
|
|
6687
|
+
${configInlineScript}${cmsInlineScript}${clientDataScripts}<style id="meno-styles"${nonceAttr}>${styleContent}</style>${pageCssTags ? `
|
|
6688
|
+
${pageCssTags}` : ""}${mergedCustomCode.head ? `
|
|
5742
6689
|
${mergedCustomCode.head}` : ""}
|
|
5743
6690
|
</head>
|
|
5744
6691
|
<body>${mergedCustomCode.bodyStart ? `
|
|
@@ -7146,8 +8093,8 @@ function singularizeWord(word) {
|
|
|
7146
8093
|
if (word.endsWith("s")) return word.slice(0, -1);
|
|
7147
8094
|
return word;
|
|
7148
8095
|
}
|
|
7149
|
-
function getNestedValue2(obj,
|
|
7150
|
-
return
|
|
8096
|
+
function getNestedValue2(obj, path5) {
|
|
8097
|
+
return path5.split(".").reduce((current, key) => {
|
|
7151
8098
|
return current && typeof current === "object" ? current[key] : void 0;
|
|
7152
8099
|
}, obj);
|
|
7153
8100
|
}
|
|
@@ -8628,7 +9575,11 @@ async function createServer(config) {
|
|
|
8628
9575
|
try {
|
|
8629
9576
|
const server = await createRuntimeServer({
|
|
8630
9577
|
port: currentPort,
|
|
8631
|
-
|
|
9578
|
+
// Default `localhost` resolves to IPv6 `::1` only on macOS, so a
|
|
9579
|
+
// conventional `127.0.0.1 meno.local` hosts alias can't reach it. MENO_HOST
|
|
9580
|
+
// lets the standalone launcher bind broadly (e.g. `::` dual-stack) so both
|
|
9581
|
+
// IPv4 and IPv6 aliases work. Unset → unchanged `localhost` behavior.
|
|
9582
|
+
hostname: process.env.MENO_HOST || "localhost",
|
|
8632
9583
|
wsPath: HMR_ROUTE,
|
|
8633
9584
|
async fetch(req, upgradeWebSocket) {
|
|
8634
9585
|
const url = new URL(req.url);
|
|
@@ -9023,8 +9974,8 @@ ${plainTextErrors}`;
|
|
|
9023
9974
|
}
|
|
9024
9975
|
|
|
9025
9976
|
// lib/server/services/pageService.ts
|
|
9026
|
-
import { existsSync as
|
|
9027
|
-
import { join as join4 } from "node:path";
|
|
9977
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, mkdirSync, rmdirSync } from "node:fs";
|
|
9978
|
+
import { join as join4, relative, sep } from "node:path";
|
|
9028
9979
|
|
|
9029
9980
|
// lib/server/utils/jsonLineMapper.ts
|
|
9030
9981
|
function buildLineMap(jsonText) {
|
|
@@ -9068,13 +10019,13 @@ function buildLineMap(jsonText) {
|
|
|
9068
10019
|
pos++;
|
|
9069
10020
|
}
|
|
9070
10021
|
}
|
|
9071
|
-
function parseValue(
|
|
10022
|
+
function parseValue(path5, trackChildren) {
|
|
9072
10023
|
skipWhitespace();
|
|
9073
10024
|
const startPos = pos;
|
|
9074
10025
|
if (jsonText[pos] === "{") {
|
|
9075
|
-
parseObject(
|
|
10026
|
+
parseObject(path5, trackChildren);
|
|
9076
10027
|
} else if (jsonText[pos] === "[") {
|
|
9077
|
-
parseArray(
|
|
10028
|
+
parseArray(path5, trackChildren);
|
|
9078
10029
|
} else if (jsonText[pos] === '"') {
|
|
9079
10030
|
parseString();
|
|
9080
10031
|
} else if (jsonText[pos] === "t") {
|
|
@@ -9088,14 +10039,14 @@ function buildLineMap(jsonText) {
|
|
|
9088
10039
|
}
|
|
9089
10040
|
return { start: startPos, end: pos };
|
|
9090
10041
|
}
|
|
9091
|
-
function recordTrackedRoot(
|
|
9092
|
-
const { start, end } = parseValue(
|
|
10042
|
+
function recordTrackedRoot(path5) {
|
|
10043
|
+
const { start, end } = parseValue(path5, true);
|
|
9093
10044
|
lineMap.set("", {
|
|
9094
10045
|
startLine: charToLine[start] ?? 0,
|
|
9095
10046
|
endLine: charToLine[end - 1] || charToLine[start] || 0
|
|
9096
10047
|
});
|
|
9097
10048
|
}
|
|
9098
|
-
function parseObject(
|
|
10049
|
+
function parseObject(path5, trackChildren) {
|
|
9099
10050
|
pos++;
|
|
9100
10051
|
skipWhitespace();
|
|
9101
10052
|
while (pos < jsonText.length && jsonText[pos] !== "}") {
|
|
@@ -9104,14 +10055,14 @@ function buildLineMap(jsonText) {
|
|
|
9104
10055
|
skipWhitespace();
|
|
9105
10056
|
pos++;
|
|
9106
10057
|
skipWhitespace();
|
|
9107
|
-
if (key === "root" &&
|
|
9108
|
-
recordTrackedRoot(
|
|
9109
|
-
} else if (key === "component" &&
|
|
9110
|
-
parseComponentWrapper(
|
|
10058
|
+
if (key === "root" && path5.length === 0) {
|
|
10059
|
+
recordTrackedRoot(path5);
|
|
10060
|
+
} else if (key === "component" && path5.length === 0) {
|
|
10061
|
+
parseComponentWrapper(path5);
|
|
9111
10062
|
} else if (key === "children" && trackChildren) {
|
|
9112
|
-
parseValue(
|
|
10063
|
+
parseValue(path5, true);
|
|
9113
10064
|
} else {
|
|
9114
|
-
parseValue(
|
|
10065
|
+
parseValue(path5, false);
|
|
9115
10066
|
}
|
|
9116
10067
|
skipWhitespace();
|
|
9117
10068
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9119,9 +10070,9 @@ function buildLineMap(jsonText) {
|
|
|
9119
10070
|
}
|
|
9120
10071
|
pos++;
|
|
9121
10072
|
}
|
|
9122
|
-
function parseComponentWrapper(
|
|
10073
|
+
function parseComponentWrapper(path5) {
|
|
9123
10074
|
if (jsonText[pos] !== "{") {
|
|
9124
|
-
parseValue(
|
|
10075
|
+
parseValue(path5, false);
|
|
9125
10076
|
return;
|
|
9126
10077
|
}
|
|
9127
10078
|
pos++;
|
|
@@ -9133,9 +10084,9 @@ function buildLineMap(jsonText) {
|
|
|
9133
10084
|
pos++;
|
|
9134
10085
|
skipWhitespace();
|
|
9135
10086
|
if (key === "structure") {
|
|
9136
|
-
recordTrackedRoot(
|
|
10087
|
+
recordTrackedRoot(path5);
|
|
9137
10088
|
} else {
|
|
9138
|
-
parseValue(
|
|
10089
|
+
parseValue(path5, false);
|
|
9139
10090
|
}
|
|
9140
10091
|
skipWhitespace();
|
|
9141
10092
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9143,12 +10094,12 @@ function buildLineMap(jsonText) {
|
|
|
9143
10094
|
}
|
|
9144
10095
|
pos++;
|
|
9145
10096
|
}
|
|
9146
|
-
function parseArray(
|
|
10097
|
+
function parseArray(path5, trackChildren) {
|
|
9147
10098
|
pos++;
|
|
9148
10099
|
skipWhitespace();
|
|
9149
10100
|
let index = 0;
|
|
9150
10101
|
while (pos < jsonText.length && jsonText[pos] !== "]") {
|
|
9151
|
-
const childPath = [...
|
|
10102
|
+
const childPath = [...path5, index];
|
|
9152
10103
|
const { start, end } = parseValue(childPath, trackChildren);
|
|
9153
10104
|
if (trackChildren) {
|
|
9154
10105
|
const pathStr = childPath.join(",");
|
|
@@ -9221,9 +10172,12 @@ var PageService = class {
|
|
|
9221
10172
|
return;
|
|
9222
10173
|
}
|
|
9223
10174
|
const pages = await this.provider.loadAll();
|
|
9224
|
-
for (const [
|
|
10175
|
+
for (const [path5, content] of pages) {
|
|
9225
10176
|
const lineMap = buildLineMap(content);
|
|
9226
|
-
this.pageCache.set(
|
|
10177
|
+
this.pageCache.set(path5, content, lineMap);
|
|
10178
|
+
}
|
|
10179
|
+
for (const path5 of this.pageCache.keys()) {
|
|
10180
|
+
if (!pages.has(path5)) this.pageCache.delete(path5);
|
|
9227
10181
|
}
|
|
9228
10182
|
}
|
|
9229
10183
|
/**
|
|
@@ -9240,8 +10194,8 @@ var PageService = class {
|
|
|
9240
10194
|
* }
|
|
9241
10195
|
* ```
|
|
9242
10196
|
*/
|
|
9243
|
-
getPage(
|
|
9244
|
-
return this.pageCache.getContent(
|
|
10197
|
+
getPage(path5) {
|
|
10198
|
+
return this.pageCache.getContent(path5);
|
|
9245
10199
|
}
|
|
9246
10200
|
/**
|
|
9247
10201
|
* Get parsed page data
|
|
@@ -9260,8 +10214,8 @@ var PageService = class {
|
|
|
9260
10214
|
* }
|
|
9261
10215
|
* ```
|
|
9262
10216
|
*/
|
|
9263
|
-
getPageData(
|
|
9264
|
-
const content = this.pageCache.getContent(
|
|
10217
|
+
getPageData(path5) {
|
|
10218
|
+
const content = this.pageCache.getContent(path5);
|
|
9265
10219
|
if (!content) {
|
|
9266
10220
|
return null;
|
|
9267
10221
|
}
|
|
@@ -9291,15 +10245,15 @@ var PageService = class {
|
|
|
9291
10245
|
* });
|
|
9292
10246
|
* ```
|
|
9293
10247
|
*/
|
|
9294
|
-
async savePage(
|
|
10248
|
+
async savePage(path5, data) {
|
|
9295
10249
|
if (!this.provider) {
|
|
9296
10250
|
throw new Error("PageProvider not set");
|
|
9297
10251
|
}
|
|
9298
10252
|
const { _lineMap, ...dataToSave } = data;
|
|
9299
10253
|
const content = JSON.stringify(dataToSave, null, 2);
|
|
9300
|
-
await this.provider.save(
|
|
10254
|
+
await this.provider.save(path5, content);
|
|
9301
10255
|
const lineMap = buildLineMap(content);
|
|
9302
|
-
this.pageCache.set(
|
|
10256
|
+
this.pageCache.set(path5, content, lineMap);
|
|
9303
10257
|
}
|
|
9304
10258
|
/**
|
|
9305
10259
|
* Re-read a single page from disk via the provider and refresh the cache.
|
|
@@ -9313,22 +10267,22 @@ var PageService = class {
|
|
|
9313
10267
|
* partial mid-write or parse error), in which case the cache is left intact and
|
|
9314
10268
|
* the next watcher event retries.
|
|
9315
10269
|
*/
|
|
9316
|
-
async reloadPageFromDisk(
|
|
10270
|
+
async reloadPageFromDisk(path5) {
|
|
9317
10271
|
if (!this.provider) return false;
|
|
9318
10272
|
let content = null;
|
|
9319
10273
|
try {
|
|
9320
|
-
content = await this.provider.get(
|
|
10274
|
+
content = await this.provider.get(path5);
|
|
9321
10275
|
} catch (error) {
|
|
9322
|
-
logRuntimeError("pageService.reloadPageFromDisk", error, { path:
|
|
10276
|
+
logRuntimeError("pageService.reloadPageFromDisk", error, { path: path5 });
|
|
9323
10277
|
return false;
|
|
9324
10278
|
}
|
|
9325
10279
|
if (content) {
|
|
9326
|
-
this.pageCache.set(
|
|
10280
|
+
this.pageCache.set(path5, content, buildLineMap(content));
|
|
9327
10281
|
return true;
|
|
9328
10282
|
}
|
|
9329
10283
|
try {
|
|
9330
|
-
if (!await this.provider.exists(
|
|
9331
|
-
this.pageCache.delete(
|
|
10284
|
+
if (!await this.provider.exists(path5)) {
|
|
10285
|
+
this.pageCache.delete(path5);
|
|
9332
10286
|
return true;
|
|
9333
10287
|
}
|
|
9334
10288
|
} catch {
|
|
@@ -9351,10 +10305,10 @@ var PageService = class {
|
|
|
9351
10305
|
* await pageService.deletePage('/old-page', true);
|
|
9352
10306
|
* ```
|
|
9353
10307
|
*/
|
|
9354
|
-
async deletePage(
|
|
9355
|
-
this.pageCache.delete(
|
|
10308
|
+
async deletePage(path5, deleteFromStorage = false) {
|
|
10309
|
+
this.pageCache.delete(path5);
|
|
9356
10310
|
if (deleteFromStorage && this.provider) {
|
|
9357
|
-
await this.provider.delete(
|
|
10311
|
+
await this.provider.delete(path5);
|
|
9358
10312
|
}
|
|
9359
10313
|
}
|
|
9360
10314
|
/**
|
|
@@ -9382,26 +10336,34 @@ var PageService = class {
|
|
|
9382
10336
|
*
|
|
9383
10337
|
* @example
|
|
9384
10338
|
* ```typescript
|
|
9385
|
-
* const pages = pageService.getAllPagesWithInfo();
|
|
10339
|
+
* const pages = await pageService.getAllPagesWithInfo();
|
|
9386
10340
|
* // [{ path: "/", isDraft: false }, { path: "/about", isDraft: true }]
|
|
9387
10341
|
* ```
|
|
9388
10342
|
*/
|
|
9389
|
-
getAllPagesWithInfo() {
|
|
10343
|
+
async getAllPagesWithInfo() {
|
|
9390
10344
|
const paths = this.pageCache.keys().filter((p) => !p.startsWith("/_sketch/"));
|
|
9391
|
-
return
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
10345
|
+
return Promise.all(
|
|
10346
|
+
paths.map(async (path5) => {
|
|
10347
|
+
const pageData = this.getPageData(path5);
|
|
10348
|
+
const pathWithoutSlash = path5 === "/" ? "index" : path5.substring(1);
|
|
10349
|
+
const slashIndex = pathWithoutSlash.lastIndexOf("/");
|
|
10350
|
+
const folder = slashIndex > 0 ? pathWithoutSlash.substring(0, slashIndex) : void 0;
|
|
10351
|
+
const metaSource = pageData?.meta?.source;
|
|
10352
|
+
const source = metaSource === "cms" || metaSource === "ssr" ? metaSource : void 0;
|
|
10353
|
+
let sourcePath;
|
|
10354
|
+
if (source === "cms" && this.provider?.resolveSourcePath) {
|
|
10355
|
+
const abs = await this.provider.resolveSourcePath(path5);
|
|
10356
|
+
if (abs) sourcePath = relative(getProjectRoot(), abs).split(sep).join("/");
|
|
10357
|
+
}
|
|
10358
|
+
return {
|
|
10359
|
+
path: path5,
|
|
10360
|
+
isDraft: pageData?.meta?.draft === true,
|
|
10361
|
+
...folder ? { folder } : {},
|
|
10362
|
+
...source ? { source } : {},
|
|
10363
|
+
...sourcePath ? { sourcePath } : {}
|
|
10364
|
+
};
|
|
10365
|
+
})
|
|
10366
|
+
);
|
|
9405
10367
|
}
|
|
9406
10368
|
/**
|
|
9407
10369
|
* Get line map for a page
|
|
@@ -9411,8 +10373,8 @@ var PageService = class {
|
|
|
9411
10373
|
* @param path - Page path (e.g., "/" or "/about")
|
|
9412
10374
|
* @returns LineMap object or undefined if page not found
|
|
9413
10375
|
*/
|
|
9414
|
-
getLineMap(
|
|
9415
|
-
return this.pageCache.getLineMap(
|
|
10376
|
+
getLineMap(path5) {
|
|
10377
|
+
return this.pageCache.getLineMap(path5);
|
|
9416
10378
|
}
|
|
9417
10379
|
/**
|
|
9418
10380
|
* Pages base directory — provider-aware (e.g. `src/pages` for astro projects),
|
|
@@ -9436,12 +10398,12 @@ var PageService = class {
|
|
|
9436
10398
|
*/
|
|
9437
10399
|
getAllFolders() {
|
|
9438
10400
|
const pagesDir = this.pagesBaseDir();
|
|
9439
|
-
if (!
|
|
10401
|
+
if (!existsSync6(pagesDir)) {
|
|
9440
10402
|
return [];
|
|
9441
10403
|
}
|
|
9442
10404
|
const collectFolders = (dir, prefix) => {
|
|
9443
10405
|
try {
|
|
9444
|
-
const entries =
|
|
10406
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
9445
10407
|
const result = [];
|
|
9446
10408
|
for (const entry of entries) {
|
|
9447
10409
|
if (!entry.isDirectory()) continue;
|
|
@@ -9479,7 +10441,7 @@ var PageService = class {
|
|
|
9479
10441
|
}
|
|
9480
10442
|
const pagesDir = this.pagesBaseDir();
|
|
9481
10443
|
const folderPath = join4(pagesDir, trimmed);
|
|
9482
|
-
if (
|
|
10444
|
+
if (existsSync6(folderPath)) {
|
|
9483
10445
|
throw new Error("Folder already exists");
|
|
9484
10446
|
}
|
|
9485
10447
|
mkdirSync(folderPath, { recursive: true });
|
|
@@ -9507,11 +10469,11 @@ var PageService = class {
|
|
|
9507
10469
|
}
|
|
9508
10470
|
if (newFolder) {
|
|
9509
10471
|
const targetDir = join4(pagesDir, newFolder);
|
|
9510
|
-
if (!
|
|
10472
|
+
if (!existsSync6(targetDir)) {
|
|
9511
10473
|
mkdirSync(targetDir, { recursive: true });
|
|
9512
10474
|
}
|
|
9513
10475
|
}
|
|
9514
|
-
if (!
|
|
10476
|
+
if (!existsSync6(sourceFile)) {
|
|
9515
10477
|
throw new Error(`Page not found: ${pagePath}`);
|
|
9516
10478
|
}
|
|
9517
10479
|
await rename2(sourceFile, targetFile);
|
|
@@ -9526,7 +10488,7 @@ var PageService = class {
|
|
|
9526
10488
|
const sourceDir = slashIndex >= 0 ? join4(pagesDir, pageName.substring(0, slashIndex)) : null;
|
|
9527
10489
|
if (sourceDir && sourceDir !== pagesDir) {
|
|
9528
10490
|
try {
|
|
9529
|
-
const remaining =
|
|
10491
|
+
const remaining = readdirSync3(sourceDir);
|
|
9530
10492
|
if (remaining.length === 0) {
|
|
9531
10493
|
rmdirSync(sourceDir);
|
|
9532
10494
|
}
|
|
@@ -9574,10 +10536,10 @@ var PageService = class {
|
|
|
9574
10536
|
const ext = this.pageExt();
|
|
9575
10537
|
const sourceFile = join4(pagesDir, `${oldName}${ext}`);
|
|
9576
10538
|
const targetFile = join4(pagesDir, `${newName}${ext}`);
|
|
9577
|
-
if (!
|
|
10539
|
+
if (!existsSync6(sourceFile)) {
|
|
9578
10540
|
throw new Error(`Page not found: ${oldPath}`);
|
|
9579
10541
|
}
|
|
9580
|
-
if (
|
|
10542
|
+
if (existsSync6(targetFile)) {
|
|
9581
10543
|
throw new Error(`Page already exists: ${newPath}`);
|
|
9582
10544
|
}
|
|
9583
10545
|
await rename2(sourceFile, targetFile);
|
|
@@ -9605,17 +10567,17 @@ var PageService = class {
|
|
|
9605
10567
|
getSlugMappings() {
|
|
9606
10568
|
const mappings = [];
|
|
9607
10569
|
const paths = this.pageCache.keys();
|
|
9608
|
-
for (const
|
|
9609
|
-
const pageData = this.getPageData(
|
|
10570
|
+
for (const path5 of paths) {
|
|
10571
|
+
const pageData = this.getPageData(path5);
|
|
9610
10572
|
if (pageData?.meta?.slugs) {
|
|
9611
|
-
const pageId =
|
|
10573
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
9612
10574
|
mappings.push({
|
|
9613
10575
|
pageId,
|
|
9614
10576
|
slugs: pageData.meta.slugs
|
|
9615
10577
|
});
|
|
9616
10578
|
} else {
|
|
9617
|
-
const pageId =
|
|
9618
|
-
const defaultSlug =
|
|
10579
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
10580
|
+
const defaultSlug = path5 === "/" ? "" : path5.substring(1);
|
|
9619
10581
|
mappings.push({
|
|
9620
10582
|
pageId,
|
|
9621
10583
|
slugs: { _default: defaultSlug }
|
|
@@ -9628,7 +10590,7 @@ var PageService = class {
|
|
|
9628
10590
|
|
|
9629
10591
|
// lib/server/services/componentService.ts
|
|
9630
10592
|
import { join as join5 } from "node:path";
|
|
9631
|
-
import { existsSync as
|
|
10593
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync4, rmdirSync as rmdirSync2 } from "node:fs";
|
|
9632
10594
|
var ComponentService = class {
|
|
9633
10595
|
components = /* @__PURE__ */ new Map();
|
|
9634
10596
|
componentCategories = /* @__PURE__ */ new Map();
|
|
@@ -9637,6 +10599,36 @@ var ComponentService = class {
|
|
|
9637
10599
|
writer;
|
|
9638
10600
|
loadErrors = /* @__PURE__ */ new Map();
|
|
9639
10601
|
loadWarnings = [];
|
|
10602
|
+
/** Per-component serialization of whole-file re-emits. @see withComponentWriteLock */
|
|
10603
|
+
writeLocks = /* @__PURE__ */ new Map();
|
|
10604
|
+
/**
|
|
10605
|
+
* Serialize whole-file re-emits for one component.
|
|
10606
|
+
*
|
|
10607
|
+
* `saveComponentCSS` and `saveComponentJavaScript` each read the in-memory
|
|
10608
|
+
* definition, re-emit the ENTIRE `.astro` file, then write the result back.
|
|
10609
|
+
* Fired concurrently — the Custom Code modal's Save issues a JS save AND a CSS
|
|
10610
|
+
* save together — their read-modify-write races: each reads a snapshot taken
|
|
10611
|
+
* before the other's `components.set`, so the writer that lands last clobbers
|
|
10612
|
+
* the other field with a stale value (a fresh CSS edit silently reverting is
|
|
10613
|
+
* the visible symptom). Chaining per component name makes each op observe the
|
|
10614
|
+
* previous op's committed state. Prior failures are swallowed on the chain tail
|
|
10615
|
+
* so one bad write can't wedge the queue.
|
|
10616
|
+
*/
|
|
10617
|
+
withComponentWriteLock(name, fn) {
|
|
10618
|
+
const prev = this.writeLocks.get(name) ?? Promise.resolve();
|
|
10619
|
+
const run = prev.then(fn, fn);
|
|
10620
|
+
const tail = run.then(
|
|
10621
|
+
() => {
|
|
10622
|
+
},
|
|
10623
|
+
() => {
|
|
10624
|
+
}
|
|
10625
|
+
);
|
|
10626
|
+
this.writeLocks.set(name, tail);
|
|
10627
|
+
tail.finally(() => {
|
|
10628
|
+
if (this.writeLocks.get(name) === tail) this.writeLocks.delete(name);
|
|
10629
|
+
});
|
|
10630
|
+
return run;
|
|
10631
|
+
}
|
|
9640
10632
|
/**
|
|
9641
10633
|
* Creates a new ComponentService instance
|
|
9642
10634
|
*
|
|
@@ -9891,12 +10883,12 @@ var ComponentService = class {
|
|
|
9891
10883
|
*/
|
|
9892
10884
|
findComponentCategoryOnDisk(name) {
|
|
9893
10885
|
const componentsDir = this.componentsBaseDir();
|
|
9894
|
-
if (!
|
|
10886
|
+
if (!existsSync7(componentsDir)) return void 0;
|
|
9895
10887
|
const ext = this.writer ? null : ".json";
|
|
9896
10888
|
const existsAt = (dir) => {
|
|
9897
|
-
if (ext) return
|
|
10889
|
+
if (ext) return existsSync7(join5(dir, `${name}${ext}`));
|
|
9898
10890
|
try {
|
|
9899
|
-
return
|
|
10891
|
+
return readdirSync4(dir, { withFileTypes: true }).some(
|
|
9900
10892
|
(e) => e.isFile() && (e.name === `${name}.json` || e.name === `${name}.astro`)
|
|
9901
10893
|
);
|
|
9902
10894
|
} catch {
|
|
@@ -9905,7 +10897,7 @@ var ComponentService = class {
|
|
|
9905
10897
|
};
|
|
9906
10898
|
if (existsAt(componentsDir)) return void 0;
|
|
9907
10899
|
try {
|
|
9908
|
-
for (const entry of
|
|
10900
|
+
for (const entry of readdirSync4(componentsDir, { withFileTypes: true })) {
|
|
9909
10901
|
if (entry.isDirectory() && existsAt(join5(componentsDir, entry.name))) {
|
|
9910
10902
|
return entry.name;
|
|
9911
10903
|
}
|
|
@@ -9986,7 +10978,7 @@ var ComponentService = class {
|
|
|
9986
10978
|
await this.writer.writeComponent(componentDir, name, dataWithJS);
|
|
9987
10979
|
} else {
|
|
9988
10980
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
9989
|
-
if (targetCategory && !
|
|
10981
|
+
if (targetCategory && !existsSync7(componentDir)) {
|
|
9990
10982
|
mkdirSync2(componentDir, { recursive: true });
|
|
9991
10983
|
}
|
|
9992
10984
|
const filePath = join5(componentDir, `${name}.json`);
|
|
@@ -10015,10 +11007,12 @@ var ComponentService = class {
|
|
|
10015
11007
|
async saveComponentJavaScript(name, javascript) {
|
|
10016
11008
|
const componentDir = this.getComponentDir(name);
|
|
10017
11009
|
if (this.writer) {
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
11010
|
+
await this.withComponentWriteLock(name, async () => {
|
|
11011
|
+
const current = this.components.get(name);
|
|
11012
|
+
await this.writer.writeJavaScript(componentDir, name, javascript || "", current);
|
|
11013
|
+
const next = current ? { ...current, component: { ...current.component, javascript: javascript || "" } } : { component: { javascript: javascript || "" } };
|
|
11014
|
+
this.components.set(name, next);
|
|
11015
|
+
});
|
|
10022
11016
|
return;
|
|
10023
11017
|
}
|
|
10024
11018
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10061,10 +11055,12 @@ var ComponentService = class {
|
|
|
10061
11055
|
async saveComponentCSS(name, css) {
|
|
10062
11056
|
const componentDir = this.getComponentDir(name);
|
|
10063
11057
|
if (this.writer) {
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
11058
|
+
await this.withComponentWriteLock(name, async () => {
|
|
11059
|
+
const current = this.components.get(name);
|
|
11060
|
+
await this.writer.writeCSS(componentDir, name, css || "", current);
|
|
11061
|
+
const next = current ? { ...current, component: { ...current.component, css: css || "" } } : { component: { css: css || "" } };
|
|
11062
|
+
this.components.set(name, next);
|
|
11063
|
+
});
|
|
10068
11064
|
return;
|
|
10069
11065
|
}
|
|
10070
11066
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10103,11 +11099,11 @@ var ComponentService = class {
|
|
|
10103
11099
|
*/
|
|
10104
11100
|
getAllFolders() {
|
|
10105
11101
|
const componentsDir = this.componentsBaseDir();
|
|
10106
|
-
if (!
|
|
11102
|
+
if (!existsSync7(componentsDir)) {
|
|
10107
11103
|
return [];
|
|
10108
11104
|
}
|
|
10109
11105
|
try {
|
|
10110
|
-
const entries =
|
|
11106
|
+
const entries = readdirSync4(componentsDir, { withFileTypes: true });
|
|
10111
11107
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
10112
11108
|
} catch {
|
|
10113
11109
|
return [];
|
|
@@ -10139,7 +11135,7 @@ var ComponentService = class {
|
|
|
10139
11135
|
}
|
|
10140
11136
|
const componentsDir = this.componentsBaseDir();
|
|
10141
11137
|
const folderPath = join5(componentsDir, folderName);
|
|
10142
|
-
if (
|
|
11138
|
+
if (existsSync7(folderPath)) {
|
|
10143
11139
|
throw new Error("Folder already exists");
|
|
10144
11140
|
}
|
|
10145
11141
|
mkdirSync2(folderPath, { recursive: true });
|
|
@@ -10177,7 +11173,7 @@ var ComponentService = class {
|
|
|
10177
11173
|
if (this.writer) {
|
|
10178
11174
|
await this.writer.moveComponent(sourceDir, targetDir, name);
|
|
10179
11175
|
} else {
|
|
10180
|
-
if (targetCategory && !
|
|
11176
|
+
if (targetCategory && !existsSync7(targetDir)) {
|
|
10181
11177
|
mkdirSync2(targetDir, { recursive: true });
|
|
10182
11178
|
}
|
|
10183
11179
|
const extensions = [".json", ".js", ".css"];
|
|
@@ -10185,7 +11181,7 @@ var ComponentService = class {
|
|
|
10185
11181
|
for (const ext of extensions) {
|
|
10186
11182
|
const sourcePath = join5(sourceDir, `${name}${ext}`);
|
|
10187
11183
|
const targetPath = join5(targetDir, `${name}${ext}`);
|
|
10188
|
-
if (
|
|
11184
|
+
if (existsSync7(sourcePath)) {
|
|
10189
11185
|
await rename2(sourcePath, targetPath);
|
|
10190
11186
|
}
|
|
10191
11187
|
}
|
|
@@ -10193,7 +11189,7 @@ var ComponentService = class {
|
|
|
10193
11189
|
this.componentCategories.set(name, targetCategory);
|
|
10194
11190
|
if (currentCategory) {
|
|
10195
11191
|
try {
|
|
10196
|
-
const remaining =
|
|
11192
|
+
const remaining = readdirSync4(sourceDir);
|
|
10197
11193
|
if (remaining.length === 0) {
|
|
10198
11194
|
rmdirSync2(sourceDir);
|
|
10199
11195
|
}
|
|
@@ -10248,7 +11244,7 @@ var ComponentService = class {
|
|
|
10248
11244
|
await this.writer.moveComponent(dir, dir, oldName, trimmedNew);
|
|
10249
11245
|
} else {
|
|
10250
11246
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10251
|
-
if (
|
|
11247
|
+
if (existsSync7(join5(dir, `${trimmedNew}${ext}`))) {
|
|
10252
11248
|
throw new Error(`File "${trimmedNew}${ext}" already exists in components/${category ?? ""}`);
|
|
10253
11249
|
}
|
|
10254
11250
|
}
|
|
@@ -10256,7 +11252,7 @@ var ComponentService = class {
|
|
|
10256
11252
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10257
11253
|
const src = join5(dir, `${oldName}${ext}`);
|
|
10258
11254
|
const dst = join5(dir, `${trimmedNew}${ext}`);
|
|
10259
|
-
if (
|
|
11255
|
+
if (existsSync7(src)) {
|
|
10260
11256
|
await rename2(src, dst);
|
|
10261
11257
|
}
|
|
10262
11258
|
}
|
|
@@ -10290,11 +11286,11 @@ var ComponentService = class {
|
|
|
10290
11286
|
let pageRefs = 0;
|
|
10291
11287
|
if (pageService) {
|
|
10292
11288
|
const pagePaths = pageService.getAllPagePaths();
|
|
10293
|
-
for (const
|
|
10294
|
-
const pageData = pageService.getPageData(
|
|
11289
|
+
for (const path5 of pagePaths) {
|
|
11290
|
+
const pageData = pageService.getPageData(path5);
|
|
10295
11291
|
if (!pageData || !("root" in pageData) || !pageData.root) continue;
|
|
10296
11292
|
if (rewriteComponentRefs(pageData.root, oldName, trimmedNew)) {
|
|
10297
|
-
await pageService.savePage(
|
|
11293
|
+
await pageService.savePage(path5, pageData);
|
|
10298
11294
|
pageRefs++;
|
|
10299
11295
|
}
|
|
10300
11296
|
}
|
|
@@ -10452,12 +11448,12 @@ var CMSService = class {
|
|
|
10452
11448
|
* @param locale - Optional locale for i18n slug matching
|
|
10453
11449
|
* @returns CMSRouteMatch if matched, null otherwise
|
|
10454
11450
|
*/
|
|
10455
|
-
async matchRoute(
|
|
11451
|
+
async matchRoute(path5, locale) {
|
|
10456
11452
|
if (!this.provider) {
|
|
10457
11453
|
return null;
|
|
10458
11454
|
}
|
|
10459
11455
|
for (const route of this.routePatterns) {
|
|
10460
|
-
const match =
|
|
11456
|
+
const match = path5.match(route.regex);
|
|
10461
11457
|
if (match) {
|
|
10462
11458
|
const urlSlug = match[route.slugGroup];
|
|
10463
11459
|
if (urlSlug === void 0) continue;
|
|
@@ -10566,11 +11562,11 @@ var CMSService = class {
|
|
|
10566
11562
|
if (options?.excludeDrafts && isItemDraftForLocale(item, locale)) continue;
|
|
10567
11563
|
const localizedSlug = slugValue[locale];
|
|
10568
11564
|
if (localizedSlug) {
|
|
10569
|
-
const
|
|
11565
|
+
const path5 = urlPattern.replace("{{slug}}", localizedSlug);
|
|
10570
11566
|
if (locale === defaultLocale) {
|
|
10571
|
-
urls.push(
|
|
11567
|
+
urls.push(path5);
|
|
10572
11568
|
} else {
|
|
10573
|
-
urls.push(`/${locale}${
|
|
11569
|
+
urls.push(`/${locale}${path5}`);
|
|
10574
11570
|
}
|
|
10575
11571
|
}
|
|
10576
11572
|
}
|
|
@@ -10822,7 +11818,7 @@ var CMSService = class {
|
|
|
10822
11818
|
};
|
|
10823
11819
|
|
|
10824
11820
|
// lib/server/providers/fileSystemCMSProvider.ts
|
|
10825
|
-
import { existsSync as
|
|
11821
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
10826
11822
|
import { join as join6 } from "node:path";
|
|
10827
11823
|
var log6 = createLogger("FileSystemCMSProvider");
|
|
10828
11824
|
var DRAFT_FILE_SUFFIX = `${CMS_DRAFT_SUFFIX}.json`;
|
|
@@ -10895,10 +11891,10 @@ var FileSystemCMSProvider = class {
|
|
|
10895
11891
|
return this.schemaCache;
|
|
10896
11892
|
}
|
|
10897
11893
|
const schemas = /* @__PURE__ */ new Map();
|
|
10898
|
-
if (!
|
|
11894
|
+
if (!existsSync8(this.templatesDir)) {
|
|
10899
11895
|
return schemas;
|
|
10900
11896
|
}
|
|
10901
|
-
const files =
|
|
11897
|
+
const files = readdirSync5(this.templatesDir);
|
|
10902
11898
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
10903
11899
|
const results = await Promise.all(
|
|
10904
11900
|
jsonFiles.map(async (file) => {
|
|
@@ -10928,10 +11924,10 @@ var FileSystemCMSProvider = class {
|
|
|
10928
11924
|
async getItems(collection) {
|
|
10929
11925
|
this.validateCollection(collection);
|
|
10930
11926
|
const collectionDir = join6(this.cmsDir, collection);
|
|
10931
|
-
if (!
|
|
11927
|
+
if (!existsSync8(collectionDir)) {
|
|
10932
11928
|
return [];
|
|
10933
11929
|
}
|
|
10934
|
-
const files =
|
|
11930
|
+
const files = readdirSync5(collectionDir);
|
|
10935
11931
|
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.endsWith(DRAFT_FILE_SUFFIX));
|
|
10936
11932
|
const results = await Promise.all(
|
|
10937
11933
|
jsonFiles.map(async (file) => {
|
|
@@ -11021,7 +12017,7 @@ var FileSystemCMSProvider = class {
|
|
|
11021
12017
|
}
|
|
11022
12018
|
this.validateFilename(filename);
|
|
11023
12019
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11024
|
-
if (!
|
|
12020
|
+
if (!existsSync8(collectionDir)) {
|
|
11025
12021
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11026
12022
|
}
|
|
11027
12023
|
const itemData = stripTransient(item);
|
|
@@ -11036,11 +12032,11 @@ var FileSystemCMSProvider = class {
|
|
|
11036
12032
|
this.validateFilename(filename);
|
|
11037
12033
|
const { unlink } = await import("node:fs/promises");
|
|
11038
12034
|
const publishedPath = join6(this.cmsDir, collection, `${filename}.json`);
|
|
11039
|
-
if (
|
|
12035
|
+
if (existsSync8(publishedPath)) {
|
|
11040
12036
|
await unlink(publishedPath);
|
|
11041
12037
|
}
|
|
11042
12038
|
const draftPath = this.draftPath(collection, filename);
|
|
11043
|
-
if (
|
|
12039
|
+
if (existsSync8(draftPath)) {
|
|
11044
12040
|
await unlink(draftPath);
|
|
11045
12041
|
}
|
|
11046
12042
|
}
|
|
@@ -11074,8 +12070,8 @@ var FileSystemCMSProvider = class {
|
|
|
11074
12070
|
async getAllDrafts(collection) {
|
|
11075
12071
|
this.validateCollection(collection);
|
|
11076
12072
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11077
|
-
if (!
|
|
11078
|
-
const files =
|
|
12073
|
+
if (!existsSync8(collectionDir)) return [];
|
|
12074
|
+
const files = readdirSync5(collectionDir).filter((f) => f.endsWith(DRAFT_FILE_SUFFIX));
|
|
11079
12075
|
const results = await Promise.all(
|
|
11080
12076
|
files.map(async (file) => {
|
|
11081
12077
|
const filePath = join6(collectionDir, file);
|
|
@@ -11100,7 +12096,7 @@ var FileSystemCMSProvider = class {
|
|
|
11100
12096
|
async hasDraft(collection, filename) {
|
|
11101
12097
|
this.validateCollection(collection);
|
|
11102
12098
|
this.validateFilename(filename);
|
|
11103
|
-
return
|
|
12099
|
+
return existsSync8(this.draftPath(collection, filename));
|
|
11104
12100
|
}
|
|
11105
12101
|
/**
|
|
11106
12102
|
* Save the draft version of an item. Loose validation — drafts may have
|
|
@@ -11135,7 +12131,7 @@ var FileSystemCMSProvider = class {
|
|
|
11135
12131
|
}
|
|
11136
12132
|
this.validateFilename(filename);
|
|
11137
12133
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11138
|
-
if (!
|
|
12134
|
+
if (!existsSync8(collectionDir)) {
|
|
11139
12135
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11140
12136
|
}
|
|
11141
12137
|
const itemData = stripTransient(item);
|
|
@@ -11155,7 +12151,7 @@ var FileSystemCMSProvider = class {
|
|
|
11155
12151
|
this.validateFilename(filename);
|
|
11156
12152
|
const { unlink } = await import("node:fs/promises");
|
|
11157
12153
|
const filePath = this.draftPath(collection, filename);
|
|
11158
|
-
if (
|
|
12154
|
+
if (existsSync8(filePath)) {
|
|
11159
12155
|
await unlink(filePath);
|
|
11160
12156
|
}
|
|
11161
12157
|
}
|
|
@@ -11184,13 +12180,13 @@ var FileSystemCMSProvider = class {
|
|
|
11184
12180
|
throw new Error(`Cannot publish invalid draft: ${messages}`);
|
|
11185
12181
|
}
|
|
11186
12182
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11187
|
-
if (!
|
|
12183
|
+
if (!existsSync8(collectionDir)) {
|
|
11188
12184
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11189
12185
|
}
|
|
11190
12186
|
const itemData = stripTransient(validation.data);
|
|
11191
12187
|
const publishedPath = join6(collectionDir, `${filename}.json`);
|
|
11192
12188
|
await writeFile3(publishedPath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
11193
|
-
if (
|
|
12189
|
+
if (existsSync8(draftFilePath)) {
|
|
11194
12190
|
await unlink(draftFilePath);
|
|
11195
12191
|
}
|
|
11196
12192
|
return normalizeItem(itemData, filename);
|
|
@@ -11211,18 +12207,18 @@ var FileSystemCMSProvider = class {
|
|
|
11211
12207
|
);
|
|
11212
12208
|
}
|
|
11213
12209
|
const { writeFile: writeFile3, mkdir } = await import("node:fs/promises");
|
|
11214
|
-
if (!
|
|
12210
|
+
if (!existsSync8(this.templatesDir)) {
|
|
11215
12211
|
await mkdir(this.templatesDir, { recursive: true });
|
|
11216
12212
|
}
|
|
11217
12213
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11218
|
-
if (
|
|
12214
|
+
if (existsSync8(pageFilePath)) {
|
|
11219
12215
|
throw new Error(`Page file already exists: templates/${collectionId}.json`);
|
|
11220
12216
|
}
|
|
11221
12217
|
await writeFile3(pageFilePath, JSON.stringify(pageData, null, 2), "utf-8");
|
|
11222
12218
|
const schemaSource = pageData?.meta?.cms?.source;
|
|
11223
12219
|
if (schemaSource !== "sanity") {
|
|
11224
12220
|
const collectionDir = join6(this.cmsDir, collectionId);
|
|
11225
|
-
if (!
|
|
12221
|
+
if (!existsSync8(collectionDir)) {
|
|
11226
12222
|
await mkdir(collectionDir, { recursive: true });
|
|
11227
12223
|
}
|
|
11228
12224
|
}
|
|
@@ -11238,12 +12234,12 @@ var FileSystemCMSProvider = class {
|
|
|
11238
12234
|
`Invalid collection ID: "${collectionId}". Collection IDs must contain only letters, numbers, hyphens, and underscores.`
|
|
11239
12235
|
);
|
|
11240
12236
|
}
|
|
11241
|
-
const { readFile:
|
|
12237
|
+
const { readFile: readFile3, writeFile: writeFile3 } = await import("node:fs/promises");
|
|
11242
12238
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11243
|
-
if (!
|
|
12239
|
+
if (!existsSync8(pageFilePath)) {
|
|
11244
12240
|
throw new Error(`Collection not found: ${collectionId}`);
|
|
11245
12241
|
}
|
|
11246
|
-
const content = await
|
|
12242
|
+
const content = await readFile3(pageFilePath, "utf-8");
|
|
11247
12243
|
const pageData = JSON.parse(content);
|
|
11248
12244
|
pageData.meta.cms = {
|
|
11249
12245
|
...pageData.meta.cms,
|
|
@@ -11262,26 +12258,26 @@ var PageCache = class {
|
|
|
11262
12258
|
/**
|
|
11263
12259
|
* Store page content and line map in cache
|
|
11264
12260
|
*/
|
|
11265
|
-
set(
|
|
11266
|
-
this.cache.set(
|
|
12261
|
+
set(path5, content, lineMap) {
|
|
12262
|
+
this.cache.set(path5, { content, lineMap });
|
|
11267
12263
|
}
|
|
11268
12264
|
/**
|
|
11269
12265
|
* Get cached page data
|
|
11270
12266
|
*/
|
|
11271
|
-
get(
|
|
11272
|
-
return this.cache.get(
|
|
12267
|
+
get(path5) {
|
|
12268
|
+
return this.cache.get(path5);
|
|
11273
12269
|
}
|
|
11274
12270
|
/**
|
|
11275
12271
|
* Get page content from cache
|
|
11276
12272
|
*/
|
|
11277
|
-
getContent(
|
|
11278
|
-
return this.cache.get(
|
|
12273
|
+
getContent(path5) {
|
|
12274
|
+
return this.cache.get(path5)?.content;
|
|
11279
12275
|
}
|
|
11280
12276
|
/**
|
|
11281
12277
|
* Get line map from cache
|
|
11282
12278
|
*/
|
|
11283
|
-
getLineMap(
|
|
11284
|
-
return this.cache.get(
|
|
12279
|
+
getLineMap(path5) {
|
|
12280
|
+
return this.cache.get(path5)?.lineMap;
|
|
11285
12281
|
}
|
|
11286
12282
|
/**
|
|
11287
12283
|
* Get line range for a specific element path
|
|
@@ -11292,14 +12288,14 @@ var PageCache = class {
|
|
|
11292
12288
|
/**
|
|
11293
12289
|
* Delete page from cache
|
|
11294
12290
|
*/
|
|
11295
|
-
delete(
|
|
11296
|
-
this.cache.delete(
|
|
12291
|
+
delete(path5) {
|
|
12292
|
+
this.cache.delete(path5);
|
|
11297
12293
|
}
|
|
11298
12294
|
/**
|
|
11299
12295
|
* Check if page exists in cache
|
|
11300
12296
|
*/
|
|
11301
|
-
has(
|
|
11302
|
-
return this.cache.has(
|
|
12297
|
+
has(path5) {
|
|
12298
|
+
return this.cache.has(path5);
|
|
11303
12299
|
}
|
|
11304
12300
|
/**
|
|
11305
12301
|
* Get all page paths
|
|
@@ -11338,21 +12334,21 @@ var DraftPageStore = class {
|
|
|
11338
12334
|
* Store a draft for a page path. The value is the raw JSON string the
|
|
11339
12335
|
* SSR pipeline expects, matching PageService.getPage()'s contract.
|
|
11340
12336
|
*/
|
|
11341
|
-
set(
|
|
11342
|
-
this.drafts.set(
|
|
12337
|
+
set(path5, content) {
|
|
12338
|
+
this.drafts.set(path5, content);
|
|
11343
12339
|
}
|
|
11344
12340
|
/**
|
|
11345
12341
|
* Get the current draft string for a page path, if any.
|
|
11346
12342
|
*/
|
|
11347
|
-
get(
|
|
11348
|
-
return this.drafts.get(
|
|
12343
|
+
get(path5) {
|
|
12344
|
+
return this.drafts.get(path5);
|
|
11349
12345
|
}
|
|
11350
12346
|
/**
|
|
11351
12347
|
* Drop the draft for a specific path. Called when the page is saved to
|
|
11352
12348
|
* disk so subsequent renders read the persisted version.
|
|
11353
12349
|
*/
|
|
11354
|
-
clear(
|
|
11355
|
-
this.drafts.delete(
|
|
12350
|
+
clear(path5) {
|
|
12351
|
+
this.drafts.delete(path5);
|
|
11356
12352
|
}
|
|
11357
12353
|
/**
|
|
11358
12354
|
* Drop every draft. Used on shutdown / project switch.
|
|
@@ -11360,14 +12356,19 @@ var DraftPageStore = class {
|
|
|
11360
12356
|
clearAll() {
|
|
11361
12357
|
this.drafts.clear();
|
|
11362
12358
|
}
|
|
11363
|
-
has(
|
|
11364
|
-
return this.drafts.has(
|
|
12359
|
+
has(path5) {
|
|
12360
|
+
return this.drafts.has(path5);
|
|
11365
12361
|
}
|
|
11366
12362
|
};
|
|
11367
12363
|
|
|
11368
12364
|
// lib/server/fileWatcher.ts
|
|
11369
|
-
import { watch, existsSync as
|
|
11370
|
-
import { basename as basename2, dirname as dirname2, join as join7, relative } from "node:path";
|
|
12365
|
+
import { watch, existsSync as existsSync9, statSync as statSync2, readdirSync as readdirSync6 } from "node:fs";
|
|
12366
|
+
import { basename as basename2, dirname as dirname2, join as join7, relative as relative2 } from "node:path";
|
|
12367
|
+
|
|
12368
|
+
// lib/shared/utils/fileUtils.ts
|
|
12369
|
+
var collapseFolderIndex = (name) => name.endsWith("/index") ? name.slice(0, -"/index".length) : name;
|
|
12370
|
+
|
|
12371
|
+
// lib/server/fileWatcher.ts
|
|
11371
12372
|
function watchRecursive(root, listener) {
|
|
11372
12373
|
const nested = [];
|
|
11373
12374
|
const watchedDirs = /* @__PURE__ */ new Set();
|
|
@@ -11375,17 +12376,17 @@ function watchRecursive(root, listener) {
|
|
|
11375
12376
|
const coverNewDir = (abs) => {
|
|
11376
12377
|
let isDir = false;
|
|
11377
12378
|
try {
|
|
11378
|
-
isDir =
|
|
12379
|
+
isDir = statSync2(abs).isDirectory();
|
|
11379
12380
|
} catch {
|
|
11380
12381
|
return;
|
|
11381
12382
|
}
|
|
11382
12383
|
if (!isDir || watchedDirs.has(abs)) return;
|
|
11383
12384
|
attachDir(abs);
|
|
11384
12385
|
try {
|
|
11385
|
-
for (const e of
|
|
12386
|
+
for (const e of readdirSync6(abs, { withFileTypes: true })) {
|
|
11386
12387
|
const child = join7(abs, e.name);
|
|
11387
12388
|
if (e.isDirectory()) coverNewDir(child);
|
|
11388
|
-
else listener("rename",
|
|
12389
|
+
else listener("rename", relative2(root, child));
|
|
11389
12390
|
}
|
|
11390
12391
|
} catch {
|
|
11391
12392
|
}
|
|
@@ -11398,7 +12399,7 @@ function watchRecursive(root, listener) {
|
|
|
11398
12399
|
w = watch(absDir, { recursive: true }, (event, filename) => {
|
|
11399
12400
|
if (!filename) return;
|
|
11400
12401
|
const abs = join7(absDir, filename);
|
|
11401
|
-
listener(event,
|
|
12402
|
+
listener(event, relative2(root, abs));
|
|
11402
12403
|
coverNewDir(abs);
|
|
11403
12404
|
});
|
|
11404
12405
|
} catch {
|
|
@@ -11423,19 +12424,19 @@ function watchRecursive(root, listener) {
|
|
|
11423
12424
|
return handle;
|
|
11424
12425
|
}
|
|
11425
12426
|
function attachWhenDirExists(dirPath, attach, setWatcher) {
|
|
11426
|
-
if (
|
|
12427
|
+
if (existsSync9(dirPath)) {
|
|
11427
12428
|
setWatcher(attach());
|
|
11428
12429
|
return;
|
|
11429
12430
|
}
|
|
11430
12431
|
const parentDir = dirname2(dirPath);
|
|
11431
12432
|
const targetName = basename2(dirPath);
|
|
11432
|
-
if (!
|
|
12433
|
+
if (!existsSync9(parentDir)) {
|
|
11433
12434
|
return;
|
|
11434
12435
|
}
|
|
11435
12436
|
let parentWatcher = null;
|
|
11436
12437
|
parentWatcher = watch(parentDir, (_event, filename) => {
|
|
11437
12438
|
if (filename !== targetName) return;
|
|
11438
|
-
if (!
|
|
12439
|
+
if (!existsSync9(dirPath)) return;
|
|
11439
12440
|
parentWatcher?.close();
|
|
11440
12441
|
parentWatcher = null;
|
|
11441
12442
|
setWatcher(attach());
|
|
@@ -11465,7 +12466,7 @@ var FileWatcher = class {
|
|
|
11465
12466
|
* Watches both .json and .js files to detect component definition and JavaScript changes
|
|
11466
12467
|
*/
|
|
11467
12468
|
watchComponents(dirPath = projectPaths.components()) {
|
|
11468
|
-
if (!
|
|
12469
|
+
if (!existsSync9(dirPath)) {
|
|
11469
12470
|
return;
|
|
11470
12471
|
}
|
|
11471
12472
|
this.componentsWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11481,7 +12482,7 @@ var FileWatcher = class {
|
|
|
11481
12482
|
* Start watching pages directory
|
|
11482
12483
|
*/
|
|
11483
12484
|
watchPages(dirPath = projectPaths.pages()) {
|
|
11484
|
-
if (!
|
|
12485
|
+
if (!existsSync9(dirPath)) {
|
|
11485
12486
|
return;
|
|
11486
12487
|
}
|
|
11487
12488
|
this.pagesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11521,7 +12522,7 @@ var FileWatcher = class {
|
|
|
11521
12522
|
* Start watching colors.json file
|
|
11522
12523
|
*/
|
|
11523
12524
|
watchColors(filePath = projectPaths.colors()) {
|
|
11524
|
-
if (!
|
|
12525
|
+
if (!existsSync9(filePath)) {
|
|
11525
12526
|
return;
|
|
11526
12527
|
}
|
|
11527
12528
|
const dirPath = getProjectRoot();
|
|
@@ -11578,7 +12579,7 @@ var FileWatcher = class {
|
|
|
11578
12579
|
* Start watching images directory for new source images
|
|
11579
12580
|
*/
|
|
11580
12581
|
watchImages(dirPath = projectPaths.images()) {
|
|
11581
|
-
if (!
|
|
12582
|
+
if (!existsSync9(dirPath)) {
|
|
11582
12583
|
return;
|
|
11583
12584
|
}
|
|
11584
12585
|
this.imagesWatcher = watch(dirPath, { recursive: false }, async (event, filename) => {
|
|
@@ -11605,7 +12606,7 @@ var FileWatcher = class {
|
|
|
11605
12606
|
* Start watching libraries directory for CSS/JS changes
|
|
11606
12607
|
*/
|
|
11607
12608
|
watchLibraries(dirPath = projectPaths.libraries()) {
|
|
11608
|
-
if (!
|
|
12609
|
+
if (!existsSync9(dirPath)) {
|
|
11609
12610
|
return;
|
|
11610
12611
|
}
|
|
11611
12612
|
this.librariesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11638,7 +12639,8 @@ var FileWatcher = class {
|
|
|
11638
12639
|
if (this.callbacks.onCMSTemplateChange) await this.callbacks.onCMSTemplateChange();
|
|
11639
12640
|
return;
|
|
11640
12641
|
}
|
|
11641
|
-
const
|
|
12642
|
+
const pageName = collapseFolderIndex(filename.replace(/\.astro$/, ""));
|
|
12643
|
+
const pagePath = mapPageNameToPath(pageName);
|
|
11642
12644
|
if (this.callbacks.onPageChange) await this.callbacks.onPageChange(pagePath);
|
|
11643
12645
|
}),
|
|
11644
12646
|
(w) => {
|
|
@@ -11806,9 +12808,9 @@ var FileWatcherService = class {
|
|
|
11806
12808
|
this.refreshSchemasTimer = null;
|
|
11807
12809
|
await cmsService.refreshSchemas();
|
|
11808
12810
|
const ids = new Set(cmsService.getAllSchemas().keys());
|
|
11809
|
-
for (const
|
|
11810
|
-
if (
|
|
11811
|
-
await pageService.deletePage(
|
|
12811
|
+
for (const path5 of pageService.getAllPagePaths()) {
|
|
12812
|
+
if (path5.startsWith("/templates/") && !ids.has(path5.slice("/templates/".length))) {
|
|
12813
|
+
await pageService.deletePage(path5);
|
|
11812
12814
|
}
|
|
11813
12815
|
}
|
|
11814
12816
|
for (const id of ids) {
|
|
@@ -11862,20 +12864,20 @@ var FileWatcherService = class {
|
|
|
11862
12864
|
};
|
|
11863
12865
|
|
|
11864
12866
|
// lib/server/migrateTemplates.ts
|
|
11865
|
-
import { existsSync as
|
|
12867
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
11866
12868
|
import { rename } from "node:fs/promises";
|
|
11867
12869
|
import { join as join8 } from "node:path";
|
|
11868
12870
|
async function migrateTemplatesDirectory() {
|
|
11869
12871
|
const oldDir = join8(projectPaths.pages(), "templates");
|
|
11870
12872
|
const newDir = projectPaths.templates();
|
|
11871
|
-
if (!
|
|
11872
|
-
if (
|
|
12873
|
+
if (!existsSync10(oldDir)) return;
|
|
12874
|
+
if (existsSync10(newDir)) return;
|
|
11873
12875
|
await rename(oldDir, newDir);
|
|
11874
12876
|
console.log("Migrated CMS templates: pages/templates/ \u2192 templates/");
|
|
11875
12877
|
}
|
|
11876
12878
|
|
|
11877
12879
|
// build-astro.ts
|
|
11878
|
-
import { existsSync as
|
|
12880
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync4, rmSync, statSync as statSync3, copyFileSync, writeFileSync } from "node:fs";
|
|
11879
12881
|
import { writeFile as writeFile2, readFile } from "node:fs/promises";
|
|
11880
12882
|
import { join as join9 } from "node:path";
|
|
11881
12883
|
import { createHash } from "node:crypto";
|
|
@@ -12961,8 +13963,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12961
13963
|
if (dynLiteralMatch) {
|
|
12962
13964
|
const existing = dynLiteralMatch[1] ?? "";
|
|
12963
13965
|
const trimmed = existing.trimEnd();
|
|
12964
|
-
const
|
|
12965
|
-
return ` style={\`${existing}${
|
|
13966
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
13967
|
+
return ` style={\`${existing}${sep2}${extraCss}\`}`;
|
|
12966
13968
|
}
|
|
12967
13969
|
const dynExprMatch = styleAttr.match(/^ style=\{([\s\S]*)\}$/);
|
|
12968
13970
|
if (dynExprMatch) {
|
|
@@ -12970,8 +13972,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12970
13972
|
}
|
|
12971
13973
|
return styleAttr.replace(/style="([^"]*)"/, (_, existing) => {
|
|
12972
13974
|
const trimmed = existing.trimEnd();
|
|
12973
|
-
const
|
|
12974
|
-
return `style="${existing}${
|
|
13975
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
13976
|
+
return `style="${existing}${sep2}${extraCss}"`;
|
|
12975
13977
|
});
|
|
12976
13978
|
}
|
|
12977
13979
|
var PICTURE_WIDTHS = [500, 800, 1080, 1600, 2400];
|
|
@@ -13697,7 +14699,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13697
14699
|
const code = localeConfig.code;
|
|
13698
14700
|
const isCurrent = code === currentLocale;
|
|
13699
14701
|
if (!showCurrent && isCurrent) continue;
|
|
13700
|
-
const
|
|
14702
|
+
const path5 = slugMap[code] || "/";
|
|
13701
14703
|
const classes = isCurrent ? activeItemClasses : itemClasses;
|
|
13702
14704
|
const classAttr = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
13703
14705
|
const currentAttr = isCurrent ? ' data-current="true"' : ' data-current="false"';
|
|
@@ -13722,7 +14724,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13722
14724
|
}
|
|
13723
14725
|
linkContent += `<div>${escapeJSX(displayText)}</div>`;
|
|
13724
14726
|
links.push(
|
|
13725
|
-
`${ind(ctx)} <a href="${escapeJSX(
|
|
14727
|
+
`${ind(ctx)} <a href="${escapeJSX(path5)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`
|
|
13726
14728
|
);
|
|
13727
14729
|
}
|
|
13728
14730
|
let linksContent;
|
|
@@ -14216,8 +15218,8 @@ function emitAstroPage(options) {
|
|
|
14216
15218
|
}
|
|
14217
15219
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14218
15220
|
for (const comp of componentImports) {
|
|
14219
|
-
const
|
|
14220
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15221
|
+
const path5 = componentImportPath(fileDepth, comp);
|
|
15222
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14221
15223
|
}
|
|
14222
15224
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
14223
15225
|
const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral2(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral2(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral2(libraryTags.bodyEndJS || "")}\` }`;
|
|
@@ -14556,8 +15558,8 @@ function emitCMSPage(options) {
|
|
|
14556
15558
|
}
|
|
14557
15559
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14558
15560
|
for (const comp of componentImports) {
|
|
14559
|
-
const
|
|
14560
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15561
|
+
const path5 = componentImportPath2(fileDepth, comp);
|
|
15562
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14561
15563
|
}
|
|
14562
15564
|
const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
|
|
14563
15565
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
@@ -14730,24 +15732,24 @@ function writePageScript(javascript, scriptsDir) {
|
|
|
14730
15732
|
if (!javascript) return [];
|
|
14731
15733
|
const hash = hashContent3(javascript);
|
|
14732
15734
|
const scriptFile = `${hash}.js`;
|
|
14733
|
-
if (!
|
|
15735
|
+
if (!existsSync11(scriptsDir)) {
|
|
14734
15736
|
mkdirSync4(scriptsDir, { recursive: true });
|
|
14735
15737
|
}
|
|
14736
15738
|
const fullScriptPath = join9(scriptsDir, scriptFile);
|
|
14737
|
-
if (!
|
|
15739
|
+
if (!existsSync11(fullScriptPath)) {
|
|
14738
15740
|
writeFileSync(fullScriptPath, javascript, "utf-8");
|
|
14739
15741
|
}
|
|
14740
15742
|
return [`/_scripts/${scriptFile}`];
|
|
14741
15743
|
}
|
|
14742
15744
|
function copyDirectory(src, dest, filter) {
|
|
14743
|
-
if (!
|
|
14744
|
-
if (!
|
|
14745
|
-
const files =
|
|
15745
|
+
if (!existsSync11(src)) return;
|
|
15746
|
+
if (!existsSync11(dest)) mkdirSync4(dest, { recursive: true });
|
|
15747
|
+
const files = readdirSync7(src);
|
|
14746
15748
|
for (const file of files) {
|
|
14747
15749
|
if (filter && !filter(file)) continue;
|
|
14748
15750
|
const srcPath = join9(src, file);
|
|
14749
15751
|
const destPath = join9(dest, file);
|
|
14750
|
-
const stat =
|
|
15752
|
+
const stat = statSync3(srcPath);
|
|
14751
15753
|
if (stat.isDirectory()) copyDirectory(srcPath, destPath, filter);
|
|
14752
15754
|
else copyFileSync(srcPath, destPath);
|
|
14753
15755
|
}
|
|
@@ -14763,8 +15765,8 @@ function isCMSPage(pageData) {
|
|
|
14763
15765
|
}
|
|
14764
15766
|
function scanJSONFiles(dir, prefix = "") {
|
|
14765
15767
|
const results = [];
|
|
14766
|
-
if (!
|
|
14767
|
-
const entries =
|
|
15768
|
+
if (!existsSync11(dir)) return results;
|
|
15769
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14768
15770
|
for (const entry of entries) {
|
|
14769
15771
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
14770
15772
|
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
@@ -14936,7 +15938,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14936
15938
|
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
14937
15939
|
const imageMetadataMap = await buildImageMetadataMap();
|
|
14938
15940
|
const outDir = outputDir || join9(projectPaths.project, "astro-export");
|
|
14939
|
-
if (
|
|
15941
|
+
if (existsSync11(outDir)) {
|
|
14940
15942
|
rmSync(outDir, { recursive: true, force: true });
|
|
14941
15943
|
}
|
|
14942
15944
|
mkdirSync4(outDir, { recursive: true });
|
|
@@ -14951,7 +15953,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14951
15953
|
mkdirSync4(d, { recursive: true });
|
|
14952
15954
|
}
|
|
14953
15955
|
const pagesDir = projectPaths.pages();
|
|
14954
|
-
if (!
|
|
15956
|
+
if (!existsSync11(pagesDir)) {
|
|
14955
15957
|
console.error("Pages directory not found!");
|
|
14956
15958
|
return { pages: 0, cmsPages: 0, collections: 0, errors: 1 };
|
|
14957
15959
|
}
|
|
@@ -15082,7 +16084,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15082
16084
|
const shouldInline = css.inline !== false;
|
|
15083
16085
|
const relPath = css.url.slice(1);
|
|
15084
16086
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15085
|
-
if (!
|
|
16087
|
+
if (!existsSync11(srcPath)) continue;
|
|
15086
16088
|
if (shouldInline) {
|
|
15087
16089
|
try {
|
|
15088
16090
|
inlineContents.set(css.url, await readFile(srcPath, "utf-8"));
|
|
@@ -15096,7 +16098,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15096
16098
|
for (const js of buildLibraries.js || []) {
|
|
15097
16099
|
if (js.url.startsWith("/")) {
|
|
15098
16100
|
const relPath = js.url.slice(1);
|
|
15099
|
-
if (
|
|
16101
|
+
if (existsSync11(join9(projectPaths.project, relPath))) {
|
|
15100
16102
|
localLibsToCopy.push(relPath);
|
|
15101
16103
|
}
|
|
15102
16104
|
}
|
|
@@ -15117,8 +16119,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15117
16119
|
const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
|
|
15118
16120
|
const collectionUrlExpr = /* @__PURE__ */ new Map();
|
|
15119
16121
|
const mergedRichTextFields = /* @__PURE__ */ new Set();
|
|
15120
|
-
if (
|
|
15121
|
-
for (const file of
|
|
16122
|
+
if (existsSync11(templatesDir)) {
|
|
16123
|
+
for (const file of readdirSync7(templatesDir).filter((f) => f.endsWith(".json"))) {
|
|
15122
16124
|
const tc = await loadJSONFile(join9(templatesDir, file));
|
|
15123
16125
|
if (!tc) continue;
|
|
15124
16126
|
try {
|
|
@@ -15133,8 +16135,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15133
16135
|
}
|
|
15134
16136
|
}
|
|
15135
16137
|
}
|
|
15136
|
-
if (
|
|
15137
|
-
const templateFiles =
|
|
16138
|
+
if (existsSync11(templatesDir)) {
|
|
16139
|
+
const templateFiles = readdirSync7(templatesDir).filter((f) => f.endsWith(".json"));
|
|
15138
16140
|
for (const file of templateFiles) {
|
|
15139
16141
|
const templateContent = await loadJSONFile(join9(templatesDir, file));
|
|
15140
16142
|
if (!templateContent) continue;
|
|
@@ -15227,7 +16229,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15227
16229
|
});
|
|
15228
16230
|
const astroFileFull = join9(pagesOutDir, astroFilePath);
|
|
15229
16231
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15230
|
-
if (!
|
|
16232
|
+
if (!existsSync11(astroFileDir)) {
|
|
15231
16233
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15232
16234
|
}
|
|
15233
16235
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15378,7 +16380,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
15378
16380
|
}
|
|
15379
16381
|
const astroFileFull = join9(pagesOutDir, result.astroFilePath);
|
|
15380
16382
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15381
|
-
if (!
|
|
16383
|
+
if (!existsSync11(astroFileDir)) {
|
|
15382
16384
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15383
16385
|
}
|
|
15384
16386
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15410,9 +16412,9 @@ export const GET: APIRoute = () => {
|
|
|
15410
16412
|
mkdirSync4(collectionDir, { recursive: true });
|
|
15411
16413
|
const richTextFieldNames = Object.entries(schema.fields || {}).filter(([, fd]) => fd.type === "rich-text").map(([fn]) => fn);
|
|
15412
16414
|
const cmsItemsDir = join9(projectPaths.cms(), schema.id);
|
|
15413
|
-
if (
|
|
16415
|
+
if (existsSync11(cmsItemsDir)) {
|
|
15414
16416
|
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
15415
|
-
const itemFiles =
|
|
16417
|
+
const itemFiles = readdirSync7(cmsItemsDir).filter(
|
|
15416
16418
|
(f) => f.endsWith(".json") && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`))
|
|
15417
16419
|
);
|
|
15418
16420
|
for (const itemFile of itemFiles) {
|
|
@@ -15459,26 +16461,26 @@ export { collections };
|
|
|
15459
16461
|
await writeFile2(join9(srcDir, "content.config.ts"), configContent, "utf-8");
|
|
15460
16462
|
}
|
|
15461
16463
|
const imagesSrcDir = join9(projectPaths.project, "images");
|
|
15462
|
-
if (
|
|
16464
|
+
if (existsSync11(imagesSrcDir)) {
|
|
15463
16465
|
copyDirectory(imagesSrcDir, join9(srcDir, "assets", "images"), shouldCopyImageForAstro);
|
|
15464
16466
|
copyDirectory(imagesSrcDir, join9(publicDir, "images"));
|
|
15465
16467
|
}
|
|
15466
16468
|
const publicAssetDirs = ["fonts", "icons", "videos", "assets"];
|
|
15467
16469
|
for (const dir of publicAssetDirs) {
|
|
15468
16470
|
const srcAssetDir = join9(projectPaths.project, dir);
|
|
15469
|
-
if (
|
|
16471
|
+
if (existsSync11(srcAssetDir)) {
|
|
15470
16472
|
copyDirectory(srcAssetDir, join9(publicDir, dir));
|
|
15471
16473
|
}
|
|
15472
16474
|
}
|
|
15473
16475
|
const librariesDir = join9(projectPaths.project, "libraries");
|
|
15474
|
-
if (
|
|
16476
|
+
if (existsSync11(librariesDir)) {
|
|
15475
16477
|
copyDirectory(librariesDir, join9(publicDir, "libraries"));
|
|
15476
16478
|
}
|
|
15477
16479
|
for (const relPath of localLibsToCopy) {
|
|
15478
16480
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15479
16481
|
const destPath = join9(publicDir, relPath);
|
|
15480
16482
|
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
|
|
15481
|
-
if (destDir && !
|
|
16483
|
+
if (destDir && !existsSync11(destDir)) mkdirSync4(destDir, { recursive: true });
|
|
15482
16484
|
copyFileSync(srcPath, destPath);
|
|
15483
16485
|
}
|
|
15484
16486
|
const packageJson = {
|
|
@@ -15548,6 +16550,32 @@ export default defineConfig({${siteUrl ? `
|
|
|
15548
16550
|
|
|
15549
16551
|
// lib/server/index.ts
|
|
15550
16552
|
init_constants();
|
|
16553
|
+
|
|
16554
|
+
// lib/server/webflow/templateWrapper.ts
|
|
16555
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
16556
|
+
var cachedTemplate = null;
|
|
16557
|
+
async function getWebflowTemplate(appName) {
|
|
16558
|
+
if (cachedTemplate) return cachedTemplate;
|
|
16559
|
+
const url = `https://webflow-ext.com/template/v2?name=${encodeURIComponent(appName)}`;
|
|
16560
|
+
const res = await fetch(url);
|
|
16561
|
+
if (!res.ok) throw new Error(`Failed to fetch Webflow template: ${res.status}`);
|
|
16562
|
+
cachedTemplate = await res.text();
|
|
16563
|
+
return cachedTemplate;
|
|
16564
|
+
}
|
|
16565
|
+
async function wrapInWebflowTemplate(html, manifestPath) {
|
|
16566
|
+
try {
|
|
16567
|
+
const manifest = JSON.parse(await readFile2(manifestPath, "utf-8"));
|
|
16568
|
+
const template = await getWebflowTemplate(manifest.name || "Export to Meno");
|
|
16569
|
+
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
16570
|
+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
16571
|
+
const headContent = headMatch?.[1] ?? "";
|
|
16572
|
+
const bodyContent = bodyMatch?.[1] ?? "";
|
|
16573
|
+
return template.replace("{{ui}}", headContent + bodyContent);
|
|
16574
|
+
} catch (err) {
|
|
16575
|
+
console.warn("Could not fetch Webflow wrapper template:", err.message);
|
|
16576
|
+
return html;
|
|
16577
|
+
}
|
|
16578
|
+
}
|
|
15551
16579
|
export {
|
|
15552
16580
|
CMSService,
|
|
15553
16581
|
ColorService,
|
|
@@ -15567,6 +16595,7 @@ export {
|
|
|
15567
16595
|
SERVE_PORT,
|
|
15568
16596
|
VariableService,
|
|
15569
16597
|
WebSocketManager,
|
|
16598
|
+
auditStylesheet,
|
|
15570
16599
|
buildAstroProject,
|
|
15571
16600
|
buildAttributes,
|
|
15572
16601
|
buildComponentHTML,
|
|
@@ -15574,6 +16603,7 @@ export {
|
|
|
15574
16603
|
buildLineMap,
|
|
15575
16604
|
bundleFile,
|
|
15576
16605
|
clearJSValidationCache,
|
|
16606
|
+
collectClassesFromPageData,
|
|
15577
16607
|
collectComponentCSS,
|
|
15578
16608
|
collectComponentJavaScript,
|
|
15579
16609
|
colorService,
|
|
@@ -15588,6 +16618,7 @@ export {
|
|
|
15588
16618
|
errorResponse,
|
|
15589
16619
|
escapeHtml,
|
|
15590
16620
|
extractPageMeta,
|
|
16621
|
+
extractRules,
|
|
15591
16622
|
fileExists,
|
|
15592
16623
|
formHandlerScript,
|
|
15593
16624
|
generateBuildErrorPage,
|
|
@@ -15634,12 +16665,14 @@ export {
|
|
|
15634
16665
|
readJsonFile,
|
|
15635
16666
|
readTextFile,
|
|
15636
16667
|
regenerateThemeCss,
|
|
16668
|
+
renderAuditMarkdown,
|
|
15637
16669
|
renderPageSSR,
|
|
15638
16670
|
resetFontConfig,
|
|
15639
16671
|
resolvePackagePath,
|
|
15640
16672
|
resolveProjectPath,
|
|
15641
16673
|
saveColors,
|
|
15642
16674
|
saveVariables,
|
|
16675
|
+
sectionPrefix,
|
|
15643
16676
|
serializeThemeCss,
|
|
15644
16677
|
serveFile,
|
|
15645
16678
|
setProjectRoot,
|
|
@@ -15651,6 +16684,7 @@ export {
|
|
|
15651
16684
|
watchRecursive,
|
|
15652
16685
|
withErrorHandling,
|
|
15653
16686
|
withLogging,
|
|
16687
|
+
wrapInWebflowTemplate,
|
|
15654
16688
|
writeFile
|
|
15655
16689
|
};
|
|
15656
16690
|
//# sourceMappingURL=index.js.map
|