meno-core 1.1.2 → 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-J4IPTP5X.js → chunk-UOF4MCAD.js} +92 -4
- package/dist/chunks/chunk-UOF4MCAD.js.map +7 -0
- package/dist/chunks/{chunk-7ZLF4NE5.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 +1287 -236
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +5 -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/scripts/formHandler.ts +17 -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/middleware/cors.test.ts +1 -1
- package/lib/server/middleware/cors.ts +1 -1
- 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/functions.ts +2 -2
- 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/nodeUtils.ts +18 -0
- package/lib/shared/types/cms.ts +1 -0
- package/lib/shared/utilityClassConfig.ts +2 -0
- package/lib/shared/utilityClassMapper.test.ts +63 -0
- package/lib/shared/utilityClassMapper.ts +9 -1
- package/lib/shared/utilityClassNames.ts +74 -0
- package/lib/shared/utils/fileUtils.ts +11 -0
- package/lib/shared/validation/schemas.test.ts +78 -0
- package/lib/shared/validation/schemas.ts +54 -5
- 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-J4IPTP5X.js.map +0 -7
- /package/dist/chunks/{chunk-7ZLF4NE5.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
|
/**
|
|
@@ -284,7 +285,7 @@ init_constants();
|
|
|
284
285
|
// lib/server/middleware/cors.ts
|
|
285
286
|
var DEFAULT_OPTIONS = {
|
|
286
287
|
origin: "*",
|
|
287
|
-
methods: ["GET", "POST", "PUT", "DELETE", "OPTIONS"],
|
|
288
|
+
methods: ["GET", "POST", "PUT", "PATCH", "DELETE", "OPTIONS"],
|
|
288
289
|
allowedHeaders: ["Content-Type", "Authorization"],
|
|
289
290
|
credentials: false
|
|
290
291
|
};
|
|
@@ -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)) {
|
|
@@ -2950,7 +3845,7 @@ async function handleFunctionsRoute(req, url) {
|
|
|
2950
3845
|
const response = await handler(context);
|
|
2951
3846
|
const headers = new Headers(response.headers);
|
|
2952
3847
|
headers.set("Access-Control-Allow-Origin", "*");
|
|
2953
|
-
headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, DELETE, OPTIONS");
|
|
3848
|
+
headers.set("Access-Control-Allow-Methods", "GET, POST, PUT, PATCH, DELETE, OPTIONS");
|
|
2954
3849
|
headers.set("Access-Control-Allow-Headers", "Content-Type");
|
|
2955
3850
|
return new Response(response.body, {
|
|
2956
3851
|
status: response.status,
|
|
@@ -2976,7 +3871,7 @@ function handleFunctionsPreflight(req, url) {
|
|
|
2976
3871
|
status: 204,
|
|
2977
3872
|
headers: {
|
|
2978
3873
|
"Access-Control-Allow-Origin": "*",
|
|
2979
|
-
"Access-Control-Allow-Methods": "GET, POST, PUT, DELETE, OPTIONS",
|
|
3874
|
+
"Access-Control-Allow-Methods": "GET, POST, PUT, PATCH, DELETE, OPTIONS",
|
|
2980
3875
|
"Access-Control-Allow-Headers": "Content-Type",
|
|
2981
3876
|
"Access-Control-Max-Age": "86400"
|
|
2982
3877
|
}
|
|
@@ -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
|
}
|
|
@@ -5279,7 +6186,23 @@ var formHandlerScript = `
|
|
|
5279
6186
|
// Find all forms with fetch handler
|
|
5280
6187
|
const forms = document.querySelectorAll('form[data-submit-handler="fetch"]');
|
|
5281
6188
|
|
|
6189
|
+
// Spam protection (server-enforced at /api/send-email): inject a hidden honeypot
|
|
6190
|
+
// field bots fill but humans never see, and stamp a clock-skew-free dwell time on
|
|
6191
|
+
// submit. Fields named with a leading "_" are control/meta and are not emailed.
|
|
6192
|
+
const loadedAt = Date.now();
|
|
6193
|
+
|
|
5282
6194
|
forms.forEach(function(form) {
|
|
6195
|
+
if (!form.querySelector('input[name="_honey"]')) {
|
|
6196
|
+
const hp = document.createElement('input');
|
|
6197
|
+
hp.type = 'text';
|
|
6198
|
+
hp.name = '_honey';
|
|
6199
|
+
hp.tabIndex = -1;
|
|
6200
|
+
hp.setAttribute('autocomplete', 'off');
|
|
6201
|
+
hp.setAttribute('aria-hidden', 'true');
|
|
6202
|
+
hp.style.cssText = 'position:absolute;left:-9999px;width:1px;height:1px;opacity:0';
|
|
6203
|
+
form.appendChild(hp);
|
|
6204
|
+
}
|
|
6205
|
+
|
|
5283
6206
|
form.addEventListener('submit', async function(e) {
|
|
5284
6207
|
e.preventDefault();
|
|
5285
6208
|
|
|
@@ -5314,6 +6237,7 @@ var formHandlerScript = `
|
|
|
5314
6237
|
|
|
5315
6238
|
try {
|
|
5316
6239
|
const formData = new FormData(form);
|
|
6240
|
+
formData.append('_elapsed', String(Date.now() - loadedAt));
|
|
5317
6241
|
|
|
5318
6242
|
const response = await fetch(action, {
|
|
5319
6243
|
method: method.toUpperCase(),
|
|
@@ -5437,6 +6361,7 @@ function generateAllInlineDataScripts(collections) {
|
|
|
5437
6361
|
}
|
|
5438
6362
|
|
|
5439
6363
|
// lib/server/ssr/htmlGenerator.ts
|
|
6364
|
+
import { posix } from "node:path";
|
|
5440
6365
|
function generateImagePreloadTags(preloadImages) {
|
|
5441
6366
|
if (preloadImages.length === 0) return "";
|
|
5442
6367
|
return preloadImages.map(
|
|
@@ -5447,6 +6372,43 @@ function minifyCSS(code) {
|
|
|
5447
6372
|
if (!code.trim()) return code;
|
|
5448
6373
|
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{};:,>~])\s*/g, "$1").replace(/\s+/g, " ").replace(/\{\s+/g, "{").replace(/\s+\}/g, "}").replace(/;}/g, "}").trim();
|
|
5449
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
|
+
}
|
|
5450
6412
|
async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePath = "/", baseUrl = "", useBuiltBundle = false, locale, slugMappings, cmsContext, cmsService, externalScriptPath) {
|
|
5451
6413
|
let options;
|
|
5452
6414
|
if ("pageData" in pageDataOrOptions) {
|
|
@@ -5468,7 +6430,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5468
6430
|
const {
|
|
5469
6431
|
pageData,
|
|
5470
6432
|
globalComponents: components = {},
|
|
5471
|
-
pagePath:
|
|
6433
|
+
pagePath: path5 = "/",
|
|
5472
6434
|
baseUrl: base = "",
|
|
5473
6435
|
useBuiltBundle: useBundled = false,
|
|
5474
6436
|
locale: loc,
|
|
@@ -5492,7 +6454,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5492
6454
|
const rendered = await renderPageSSR(
|
|
5493
6455
|
pageData,
|
|
5494
6456
|
components,
|
|
5495
|
-
|
|
6457
|
+
path5,
|
|
5496
6458
|
base,
|
|
5497
6459
|
loc,
|
|
5498
6460
|
void 0,
|
|
@@ -5679,7 +6641,7 @@ picture {
|
|
|
5679
6641
|
line-height: normal;
|
|
5680
6642
|
}`;
|
|
5681
6643
|
const combinedCSS = [fontCSS, themeColorVariablesCSS, variablesCSS, baseCSS, componentCSS, utilityCSS, interactiveCSS].filter(Boolean).join("\n");
|
|
5682
|
-
const cssWithStableViewport =
|
|
6644
|
+
const cssWithStableViewport = rewriteViewportUnitsInStylesheet(combinedCSS);
|
|
5683
6645
|
const finalCSS = useBundled ? minifyCSS(cssWithStableViewport) : cssWithStableViewport;
|
|
5684
6646
|
const prefetchConfig = await loadPrefetchConfig();
|
|
5685
6647
|
const menoConfig = prefetchConfig.enabled ? { prefetch: prefetchConfig } : {};
|
|
@@ -5708,6 +6670,7 @@ ${externalJavaScript}`;
|
|
|
5708
6670
|
const styleContent = useBundled ? finalCSS : `
|
|
5709
6671
|
${combinedCSS.split("\n").join("\n ")}
|
|
5710
6672
|
`;
|
|
6673
|
+
const pageCssTags = await collectPageCssImports(pageData._frontmatter, pagePath, nonceAttr);
|
|
5711
6674
|
const htmlDocument = `<!DOCTYPE html>
|
|
5712
6675
|
<html lang="${rendered.locale}" theme="${themeConfig.default}">
|
|
5713
6676
|
<head>
|
|
@@ -5721,7 +6684,8 @@ ${externalJavaScript}`;
|
|
|
5721
6684
|
` : ""}${libraryTags.headCSS ? `${libraryTags.headCSS}
|
|
5722
6685
|
` : ""}${libraryTags.headJS ? `${libraryTags.headJS}
|
|
5723
6686
|
` : ""}${rendered.meta}
|
|
5724
|
-
${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 ? `
|
|
5725
6689
|
${mergedCustomCode.head}` : ""}
|
|
5726
6690
|
</head>
|
|
5727
6691
|
<body>${mergedCustomCode.bodyStart ? `
|
|
@@ -7129,8 +8093,8 @@ function singularizeWord(word) {
|
|
|
7129
8093
|
if (word.endsWith("s")) return word.slice(0, -1);
|
|
7130
8094
|
return word;
|
|
7131
8095
|
}
|
|
7132
|
-
function getNestedValue2(obj,
|
|
7133
|
-
return
|
|
8096
|
+
function getNestedValue2(obj, path5) {
|
|
8097
|
+
return path5.split(".").reduce((current, key) => {
|
|
7134
8098
|
return current && typeof current === "object" ? current[key] : void 0;
|
|
7135
8099
|
}, obj);
|
|
7136
8100
|
}
|
|
@@ -8611,7 +9575,11 @@ async function createServer(config) {
|
|
|
8611
9575
|
try {
|
|
8612
9576
|
const server = await createRuntimeServer({
|
|
8613
9577
|
port: currentPort,
|
|
8614
|
-
|
|
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",
|
|
8615
9583
|
wsPath: HMR_ROUTE,
|
|
8616
9584
|
async fetch(req, upgradeWebSocket) {
|
|
8617
9585
|
const url = new URL(req.url);
|
|
@@ -9006,8 +9974,8 @@ ${plainTextErrors}`;
|
|
|
9006
9974
|
}
|
|
9007
9975
|
|
|
9008
9976
|
// lib/server/services/pageService.ts
|
|
9009
|
-
import { existsSync as
|
|
9010
|
-
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";
|
|
9011
9979
|
|
|
9012
9980
|
// lib/server/utils/jsonLineMapper.ts
|
|
9013
9981
|
function buildLineMap(jsonText) {
|
|
@@ -9051,13 +10019,13 @@ function buildLineMap(jsonText) {
|
|
|
9051
10019
|
pos++;
|
|
9052
10020
|
}
|
|
9053
10021
|
}
|
|
9054
|
-
function parseValue(
|
|
10022
|
+
function parseValue(path5, trackChildren) {
|
|
9055
10023
|
skipWhitespace();
|
|
9056
10024
|
const startPos = pos;
|
|
9057
10025
|
if (jsonText[pos] === "{") {
|
|
9058
|
-
parseObject(
|
|
10026
|
+
parseObject(path5, trackChildren);
|
|
9059
10027
|
} else if (jsonText[pos] === "[") {
|
|
9060
|
-
parseArray(
|
|
10028
|
+
parseArray(path5, trackChildren);
|
|
9061
10029
|
} else if (jsonText[pos] === '"') {
|
|
9062
10030
|
parseString();
|
|
9063
10031
|
} else if (jsonText[pos] === "t") {
|
|
@@ -9071,14 +10039,14 @@ function buildLineMap(jsonText) {
|
|
|
9071
10039
|
}
|
|
9072
10040
|
return { start: startPos, end: pos };
|
|
9073
10041
|
}
|
|
9074
|
-
function recordTrackedRoot(
|
|
9075
|
-
const { start, end } = parseValue(
|
|
10042
|
+
function recordTrackedRoot(path5) {
|
|
10043
|
+
const { start, end } = parseValue(path5, true);
|
|
9076
10044
|
lineMap.set("", {
|
|
9077
10045
|
startLine: charToLine[start] ?? 0,
|
|
9078
10046
|
endLine: charToLine[end - 1] || charToLine[start] || 0
|
|
9079
10047
|
});
|
|
9080
10048
|
}
|
|
9081
|
-
function parseObject(
|
|
10049
|
+
function parseObject(path5, trackChildren) {
|
|
9082
10050
|
pos++;
|
|
9083
10051
|
skipWhitespace();
|
|
9084
10052
|
while (pos < jsonText.length && jsonText[pos] !== "}") {
|
|
@@ -9087,14 +10055,14 @@ function buildLineMap(jsonText) {
|
|
|
9087
10055
|
skipWhitespace();
|
|
9088
10056
|
pos++;
|
|
9089
10057
|
skipWhitespace();
|
|
9090
|
-
if (key === "root" &&
|
|
9091
|
-
recordTrackedRoot(
|
|
9092
|
-
} else if (key === "component" &&
|
|
9093
|
-
parseComponentWrapper(
|
|
10058
|
+
if (key === "root" && path5.length === 0) {
|
|
10059
|
+
recordTrackedRoot(path5);
|
|
10060
|
+
} else if (key === "component" && path5.length === 0) {
|
|
10061
|
+
parseComponentWrapper(path5);
|
|
9094
10062
|
} else if (key === "children" && trackChildren) {
|
|
9095
|
-
parseValue(
|
|
10063
|
+
parseValue(path5, true);
|
|
9096
10064
|
} else {
|
|
9097
|
-
parseValue(
|
|
10065
|
+
parseValue(path5, false);
|
|
9098
10066
|
}
|
|
9099
10067
|
skipWhitespace();
|
|
9100
10068
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9102,9 +10070,9 @@ function buildLineMap(jsonText) {
|
|
|
9102
10070
|
}
|
|
9103
10071
|
pos++;
|
|
9104
10072
|
}
|
|
9105
|
-
function parseComponentWrapper(
|
|
10073
|
+
function parseComponentWrapper(path5) {
|
|
9106
10074
|
if (jsonText[pos] !== "{") {
|
|
9107
|
-
parseValue(
|
|
10075
|
+
parseValue(path5, false);
|
|
9108
10076
|
return;
|
|
9109
10077
|
}
|
|
9110
10078
|
pos++;
|
|
@@ -9116,9 +10084,9 @@ function buildLineMap(jsonText) {
|
|
|
9116
10084
|
pos++;
|
|
9117
10085
|
skipWhitespace();
|
|
9118
10086
|
if (key === "structure") {
|
|
9119
|
-
recordTrackedRoot(
|
|
10087
|
+
recordTrackedRoot(path5);
|
|
9120
10088
|
} else {
|
|
9121
|
-
parseValue(
|
|
10089
|
+
parseValue(path5, false);
|
|
9122
10090
|
}
|
|
9123
10091
|
skipWhitespace();
|
|
9124
10092
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9126,12 +10094,12 @@ function buildLineMap(jsonText) {
|
|
|
9126
10094
|
}
|
|
9127
10095
|
pos++;
|
|
9128
10096
|
}
|
|
9129
|
-
function parseArray(
|
|
10097
|
+
function parseArray(path5, trackChildren) {
|
|
9130
10098
|
pos++;
|
|
9131
10099
|
skipWhitespace();
|
|
9132
10100
|
let index = 0;
|
|
9133
10101
|
while (pos < jsonText.length && jsonText[pos] !== "]") {
|
|
9134
|
-
const childPath = [...
|
|
10102
|
+
const childPath = [...path5, index];
|
|
9135
10103
|
const { start, end } = parseValue(childPath, trackChildren);
|
|
9136
10104
|
if (trackChildren) {
|
|
9137
10105
|
const pathStr = childPath.join(",");
|
|
@@ -9204,9 +10172,12 @@ var PageService = class {
|
|
|
9204
10172
|
return;
|
|
9205
10173
|
}
|
|
9206
10174
|
const pages = await this.provider.loadAll();
|
|
9207
|
-
for (const [
|
|
10175
|
+
for (const [path5, content] of pages) {
|
|
9208
10176
|
const lineMap = buildLineMap(content);
|
|
9209
|
-
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);
|
|
9210
10181
|
}
|
|
9211
10182
|
}
|
|
9212
10183
|
/**
|
|
@@ -9223,8 +10194,8 @@ var PageService = class {
|
|
|
9223
10194
|
* }
|
|
9224
10195
|
* ```
|
|
9225
10196
|
*/
|
|
9226
|
-
getPage(
|
|
9227
|
-
return this.pageCache.getContent(
|
|
10197
|
+
getPage(path5) {
|
|
10198
|
+
return this.pageCache.getContent(path5);
|
|
9228
10199
|
}
|
|
9229
10200
|
/**
|
|
9230
10201
|
* Get parsed page data
|
|
@@ -9243,8 +10214,8 @@ var PageService = class {
|
|
|
9243
10214
|
* }
|
|
9244
10215
|
* ```
|
|
9245
10216
|
*/
|
|
9246
|
-
getPageData(
|
|
9247
|
-
const content = this.pageCache.getContent(
|
|
10217
|
+
getPageData(path5) {
|
|
10218
|
+
const content = this.pageCache.getContent(path5);
|
|
9248
10219
|
if (!content) {
|
|
9249
10220
|
return null;
|
|
9250
10221
|
}
|
|
@@ -9274,15 +10245,15 @@ var PageService = class {
|
|
|
9274
10245
|
* });
|
|
9275
10246
|
* ```
|
|
9276
10247
|
*/
|
|
9277
|
-
async savePage(
|
|
10248
|
+
async savePage(path5, data) {
|
|
9278
10249
|
if (!this.provider) {
|
|
9279
10250
|
throw new Error("PageProvider not set");
|
|
9280
10251
|
}
|
|
9281
10252
|
const { _lineMap, ...dataToSave } = data;
|
|
9282
10253
|
const content = JSON.stringify(dataToSave, null, 2);
|
|
9283
|
-
await this.provider.save(
|
|
10254
|
+
await this.provider.save(path5, content);
|
|
9284
10255
|
const lineMap = buildLineMap(content);
|
|
9285
|
-
this.pageCache.set(
|
|
10256
|
+
this.pageCache.set(path5, content, lineMap);
|
|
9286
10257
|
}
|
|
9287
10258
|
/**
|
|
9288
10259
|
* Re-read a single page from disk via the provider and refresh the cache.
|
|
@@ -9296,22 +10267,22 @@ var PageService = class {
|
|
|
9296
10267
|
* partial mid-write or parse error), in which case the cache is left intact and
|
|
9297
10268
|
* the next watcher event retries.
|
|
9298
10269
|
*/
|
|
9299
|
-
async reloadPageFromDisk(
|
|
10270
|
+
async reloadPageFromDisk(path5) {
|
|
9300
10271
|
if (!this.provider) return false;
|
|
9301
10272
|
let content = null;
|
|
9302
10273
|
try {
|
|
9303
|
-
content = await this.provider.get(
|
|
10274
|
+
content = await this.provider.get(path5);
|
|
9304
10275
|
} catch (error) {
|
|
9305
|
-
logRuntimeError("pageService.reloadPageFromDisk", error, { path:
|
|
10276
|
+
logRuntimeError("pageService.reloadPageFromDisk", error, { path: path5 });
|
|
9306
10277
|
return false;
|
|
9307
10278
|
}
|
|
9308
10279
|
if (content) {
|
|
9309
|
-
this.pageCache.set(
|
|
10280
|
+
this.pageCache.set(path5, content, buildLineMap(content));
|
|
9310
10281
|
return true;
|
|
9311
10282
|
}
|
|
9312
10283
|
try {
|
|
9313
|
-
if (!await this.provider.exists(
|
|
9314
|
-
this.pageCache.delete(
|
|
10284
|
+
if (!await this.provider.exists(path5)) {
|
|
10285
|
+
this.pageCache.delete(path5);
|
|
9315
10286
|
return true;
|
|
9316
10287
|
}
|
|
9317
10288
|
} catch {
|
|
@@ -9334,10 +10305,10 @@ var PageService = class {
|
|
|
9334
10305
|
* await pageService.deletePage('/old-page', true);
|
|
9335
10306
|
* ```
|
|
9336
10307
|
*/
|
|
9337
|
-
async deletePage(
|
|
9338
|
-
this.pageCache.delete(
|
|
10308
|
+
async deletePage(path5, deleteFromStorage = false) {
|
|
10309
|
+
this.pageCache.delete(path5);
|
|
9339
10310
|
if (deleteFromStorage && this.provider) {
|
|
9340
|
-
await this.provider.delete(
|
|
10311
|
+
await this.provider.delete(path5);
|
|
9341
10312
|
}
|
|
9342
10313
|
}
|
|
9343
10314
|
/**
|
|
@@ -9365,26 +10336,34 @@ var PageService = class {
|
|
|
9365
10336
|
*
|
|
9366
10337
|
* @example
|
|
9367
10338
|
* ```typescript
|
|
9368
|
-
* const pages = pageService.getAllPagesWithInfo();
|
|
10339
|
+
* const pages = await pageService.getAllPagesWithInfo();
|
|
9369
10340
|
* // [{ path: "/", isDraft: false }, { path: "/about", isDraft: true }]
|
|
9370
10341
|
* ```
|
|
9371
10342
|
*/
|
|
9372
|
-
getAllPagesWithInfo() {
|
|
10343
|
+
async getAllPagesWithInfo() {
|
|
9373
10344
|
const paths = this.pageCache.keys().filter((p) => !p.startsWith("/_sketch/"));
|
|
9374
|
-
return
|
|
9375
|
-
|
|
9376
|
-
|
|
9377
|
-
|
|
9378
|
-
|
|
9379
|
-
|
|
9380
|
-
|
|
9381
|
-
|
|
9382
|
-
|
|
9383
|
-
|
|
9384
|
-
|
|
9385
|
-
|
|
9386
|
-
|
|
9387
|
-
|
|
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
|
+
);
|
|
9388
10367
|
}
|
|
9389
10368
|
/**
|
|
9390
10369
|
* Get line map for a page
|
|
@@ -9394,8 +10373,8 @@ var PageService = class {
|
|
|
9394
10373
|
* @param path - Page path (e.g., "/" or "/about")
|
|
9395
10374
|
* @returns LineMap object or undefined if page not found
|
|
9396
10375
|
*/
|
|
9397
|
-
getLineMap(
|
|
9398
|
-
return this.pageCache.getLineMap(
|
|
10376
|
+
getLineMap(path5) {
|
|
10377
|
+
return this.pageCache.getLineMap(path5);
|
|
9399
10378
|
}
|
|
9400
10379
|
/**
|
|
9401
10380
|
* Pages base directory — provider-aware (e.g. `src/pages` for astro projects),
|
|
@@ -9419,12 +10398,12 @@ var PageService = class {
|
|
|
9419
10398
|
*/
|
|
9420
10399
|
getAllFolders() {
|
|
9421
10400
|
const pagesDir = this.pagesBaseDir();
|
|
9422
|
-
if (!
|
|
10401
|
+
if (!existsSync6(pagesDir)) {
|
|
9423
10402
|
return [];
|
|
9424
10403
|
}
|
|
9425
10404
|
const collectFolders = (dir, prefix) => {
|
|
9426
10405
|
try {
|
|
9427
|
-
const entries =
|
|
10406
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
9428
10407
|
const result = [];
|
|
9429
10408
|
for (const entry of entries) {
|
|
9430
10409
|
if (!entry.isDirectory()) continue;
|
|
@@ -9462,7 +10441,7 @@ var PageService = class {
|
|
|
9462
10441
|
}
|
|
9463
10442
|
const pagesDir = this.pagesBaseDir();
|
|
9464
10443
|
const folderPath = join4(pagesDir, trimmed);
|
|
9465
|
-
if (
|
|
10444
|
+
if (existsSync6(folderPath)) {
|
|
9466
10445
|
throw new Error("Folder already exists");
|
|
9467
10446
|
}
|
|
9468
10447
|
mkdirSync(folderPath, { recursive: true });
|
|
@@ -9490,11 +10469,11 @@ var PageService = class {
|
|
|
9490
10469
|
}
|
|
9491
10470
|
if (newFolder) {
|
|
9492
10471
|
const targetDir = join4(pagesDir, newFolder);
|
|
9493
|
-
if (!
|
|
10472
|
+
if (!existsSync6(targetDir)) {
|
|
9494
10473
|
mkdirSync(targetDir, { recursive: true });
|
|
9495
10474
|
}
|
|
9496
10475
|
}
|
|
9497
|
-
if (!
|
|
10476
|
+
if (!existsSync6(sourceFile)) {
|
|
9498
10477
|
throw new Error(`Page not found: ${pagePath}`);
|
|
9499
10478
|
}
|
|
9500
10479
|
await rename2(sourceFile, targetFile);
|
|
@@ -9509,7 +10488,7 @@ var PageService = class {
|
|
|
9509
10488
|
const sourceDir = slashIndex >= 0 ? join4(pagesDir, pageName.substring(0, slashIndex)) : null;
|
|
9510
10489
|
if (sourceDir && sourceDir !== pagesDir) {
|
|
9511
10490
|
try {
|
|
9512
|
-
const remaining =
|
|
10491
|
+
const remaining = readdirSync3(sourceDir);
|
|
9513
10492
|
if (remaining.length === 0) {
|
|
9514
10493
|
rmdirSync(sourceDir);
|
|
9515
10494
|
}
|
|
@@ -9557,10 +10536,10 @@ var PageService = class {
|
|
|
9557
10536
|
const ext = this.pageExt();
|
|
9558
10537
|
const sourceFile = join4(pagesDir, `${oldName}${ext}`);
|
|
9559
10538
|
const targetFile = join4(pagesDir, `${newName}${ext}`);
|
|
9560
|
-
if (!
|
|
10539
|
+
if (!existsSync6(sourceFile)) {
|
|
9561
10540
|
throw new Error(`Page not found: ${oldPath}`);
|
|
9562
10541
|
}
|
|
9563
|
-
if (
|
|
10542
|
+
if (existsSync6(targetFile)) {
|
|
9564
10543
|
throw new Error(`Page already exists: ${newPath}`);
|
|
9565
10544
|
}
|
|
9566
10545
|
await rename2(sourceFile, targetFile);
|
|
@@ -9588,17 +10567,17 @@ var PageService = class {
|
|
|
9588
10567
|
getSlugMappings() {
|
|
9589
10568
|
const mappings = [];
|
|
9590
10569
|
const paths = this.pageCache.keys();
|
|
9591
|
-
for (const
|
|
9592
|
-
const pageData = this.getPageData(
|
|
10570
|
+
for (const path5 of paths) {
|
|
10571
|
+
const pageData = this.getPageData(path5);
|
|
9593
10572
|
if (pageData?.meta?.slugs) {
|
|
9594
|
-
const pageId =
|
|
10573
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
9595
10574
|
mappings.push({
|
|
9596
10575
|
pageId,
|
|
9597
10576
|
slugs: pageData.meta.slugs
|
|
9598
10577
|
});
|
|
9599
10578
|
} else {
|
|
9600
|
-
const pageId =
|
|
9601
|
-
const defaultSlug =
|
|
10579
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
10580
|
+
const defaultSlug = path5 === "/" ? "" : path5.substring(1);
|
|
9602
10581
|
mappings.push({
|
|
9603
10582
|
pageId,
|
|
9604
10583
|
slugs: { _default: defaultSlug }
|
|
@@ -9611,7 +10590,7 @@ var PageService = class {
|
|
|
9611
10590
|
|
|
9612
10591
|
// lib/server/services/componentService.ts
|
|
9613
10592
|
import { join as join5 } from "node:path";
|
|
9614
|
-
import { existsSync as
|
|
10593
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync4, rmdirSync as rmdirSync2 } from "node:fs";
|
|
9615
10594
|
var ComponentService = class {
|
|
9616
10595
|
components = /* @__PURE__ */ new Map();
|
|
9617
10596
|
componentCategories = /* @__PURE__ */ new Map();
|
|
@@ -9620,6 +10599,36 @@ var ComponentService = class {
|
|
|
9620
10599
|
writer;
|
|
9621
10600
|
loadErrors = /* @__PURE__ */ new Map();
|
|
9622
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
|
+
}
|
|
9623
10632
|
/**
|
|
9624
10633
|
* Creates a new ComponentService instance
|
|
9625
10634
|
*
|
|
@@ -9874,12 +10883,12 @@ var ComponentService = class {
|
|
|
9874
10883
|
*/
|
|
9875
10884
|
findComponentCategoryOnDisk(name) {
|
|
9876
10885
|
const componentsDir = this.componentsBaseDir();
|
|
9877
|
-
if (!
|
|
10886
|
+
if (!existsSync7(componentsDir)) return void 0;
|
|
9878
10887
|
const ext = this.writer ? null : ".json";
|
|
9879
10888
|
const existsAt = (dir) => {
|
|
9880
|
-
if (ext) return
|
|
10889
|
+
if (ext) return existsSync7(join5(dir, `${name}${ext}`));
|
|
9881
10890
|
try {
|
|
9882
|
-
return
|
|
10891
|
+
return readdirSync4(dir, { withFileTypes: true }).some(
|
|
9883
10892
|
(e) => e.isFile() && (e.name === `${name}.json` || e.name === `${name}.astro`)
|
|
9884
10893
|
);
|
|
9885
10894
|
} catch {
|
|
@@ -9888,7 +10897,7 @@ var ComponentService = class {
|
|
|
9888
10897
|
};
|
|
9889
10898
|
if (existsAt(componentsDir)) return void 0;
|
|
9890
10899
|
try {
|
|
9891
|
-
for (const entry of
|
|
10900
|
+
for (const entry of readdirSync4(componentsDir, { withFileTypes: true })) {
|
|
9892
10901
|
if (entry.isDirectory() && existsAt(join5(componentsDir, entry.name))) {
|
|
9893
10902
|
return entry.name;
|
|
9894
10903
|
}
|
|
@@ -9969,7 +10978,7 @@ var ComponentService = class {
|
|
|
9969
10978
|
await this.writer.writeComponent(componentDir, name, dataWithJS);
|
|
9970
10979
|
} else {
|
|
9971
10980
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
9972
|
-
if (targetCategory && !
|
|
10981
|
+
if (targetCategory && !existsSync7(componentDir)) {
|
|
9973
10982
|
mkdirSync2(componentDir, { recursive: true });
|
|
9974
10983
|
}
|
|
9975
10984
|
const filePath = join5(componentDir, `${name}.json`);
|
|
@@ -9998,10 +11007,12 @@ var ComponentService = class {
|
|
|
9998
11007
|
async saveComponentJavaScript(name, javascript) {
|
|
9999
11008
|
const componentDir = this.getComponentDir(name);
|
|
10000
11009
|
if (this.writer) {
|
|
10001
|
-
|
|
10002
|
-
|
|
10003
|
-
|
|
10004
|
-
|
|
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
|
+
});
|
|
10005
11016
|
return;
|
|
10006
11017
|
}
|
|
10007
11018
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10044,10 +11055,12 @@ var ComponentService = class {
|
|
|
10044
11055
|
async saveComponentCSS(name, css) {
|
|
10045
11056
|
const componentDir = this.getComponentDir(name);
|
|
10046
11057
|
if (this.writer) {
|
|
10047
|
-
|
|
10048
|
-
|
|
10049
|
-
|
|
10050
|
-
|
|
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
|
+
});
|
|
10051
11064
|
return;
|
|
10052
11065
|
}
|
|
10053
11066
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10086,11 +11099,11 @@ var ComponentService = class {
|
|
|
10086
11099
|
*/
|
|
10087
11100
|
getAllFolders() {
|
|
10088
11101
|
const componentsDir = this.componentsBaseDir();
|
|
10089
|
-
if (!
|
|
11102
|
+
if (!existsSync7(componentsDir)) {
|
|
10090
11103
|
return [];
|
|
10091
11104
|
}
|
|
10092
11105
|
try {
|
|
10093
|
-
const entries =
|
|
11106
|
+
const entries = readdirSync4(componentsDir, { withFileTypes: true });
|
|
10094
11107
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
10095
11108
|
} catch {
|
|
10096
11109
|
return [];
|
|
@@ -10122,7 +11135,7 @@ var ComponentService = class {
|
|
|
10122
11135
|
}
|
|
10123
11136
|
const componentsDir = this.componentsBaseDir();
|
|
10124
11137
|
const folderPath = join5(componentsDir, folderName);
|
|
10125
|
-
if (
|
|
11138
|
+
if (existsSync7(folderPath)) {
|
|
10126
11139
|
throw new Error("Folder already exists");
|
|
10127
11140
|
}
|
|
10128
11141
|
mkdirSync2(folderPath, { recursive: true });
|
|
@@ -10160,7 +11173,7 @@ var ComponentService = class {
|
|
|
10160
11173
|
if (this.writer) {
|
|
10161
11174
|
await this.writer.moveComponent(sourceDir, targetDir, name);
|
|
10162
11175
|
} else {
|
|
10163
|
-
if (targetCategory && !
|
|
11176
|
+
if (targetCategory && !existsSync7(targetDir)) {
|
|
10164
11177
|
mkdirSync2(targetDir, { recursive: true });
|
|
10165
11178
|
}
|
|
10166
11179
|
const extensions = [".json", ".js", ".css"];
|
|
@@ -10168,7 +11181,7 @@ var ComponentService = class {
|
|
|
10168
11181
|
for (const ext of extensions) {
|
|
10169
11182
|
const sourcePath = join5(sourceDir, `${name}${ext}`);
|
|
10170
11183
|
const targetPath = join5(targetDir, `${name}${ext}`);
|
|
10171
|
-
if (
|
|
11184
|
+
if (existsSync7(sourcePath)) {
|
|
10172
11185
|
await rename2(sourcePath, targetPath);
|
|
10173
11186
|
}
|
|
10174
11187
|
}
|
|
@@ -10176,7 +11189,7 @@ var ComponentService = class {
|
|
|
10176
11189
|
this.componentCategories.set(name, targetCategory);
|
|
10177
11190
|
if (currentCategory) {
|
|
10178
11191
|
try {
|
|
10179
|
-
const remaining =
|
|
11192
|
+
const remaining = readdirSync4(sourceDir);
|
|
10180
11193
|
if (remaining.length === 0) {
|
|
10181
11194
|
rmdirSync2(sourceDir);
|
|
10182
11195
|
}
|
|
@@ -10231,7 +11244,7 @@ var ComponentService = class {
|
|
|
10231
11244
|
await this.writer.moveComponent(dir, dir, oldName, trimmedNew);
|
|
10232
11245
|
} else {
|
|
10233
11246
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10234
|
-
if (
|
|
11247
|
+
if (existsSync7(join5(dir, `${trimmedNew}${ext}`))) {
|
|
10235
11248
|
throw new Error(`File "${trimmedNew}${ext}" already exists in components/${category ?? ""}`);
|
|
10236
11249
|
}
|
|
10237
11250
|
}
|
|
@@ -10239,7 +11252,7 @@ var ComponentService = class {
|
|
|
10239
11252
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10240
11253
|
const src = join5(dir, `${oldName}${ext}`);
|
|
10241
11254
|
const dst = join5(dir, `${trimmedNew}${ext}`);
|
|
10242
|
-
if (
|
|
11255
|
+
if (existsSync7(src)) {
|
|
10243
11256
|
await rename2(src, dst);
|
|
10244
11257
|
}
|
|
10245
11258
|
}
|
|
@@ -10273,11 +11286,11 @@ var ComponentService = class {
|
|
|
10273
11286
|
let pageRefs = 0;
|
|
10274
11287
|
if (pageService) {
|
|
10275
11288
|
const pagePaths = pageService.getAllPagePaths();
|
|
10276
|
-
for (const
|
|
10277
|
-
const pageData = pageService.getPageData(
|
|
11289
|
+
for (const path5 of pagePaths) {
|
|
11290
|
+
const pageData = pageService.getPageData(path5);
|
|
10278
11291
|
if (!pageData || !("root" in pageData) || !pageData.root) continue;
|
|
10279
11292
|
if (rewriteComponentRefs(pageData.root, oldName, trimmedNew)) {
|
|
10280
|
-
await pageService.savePage(
|
|
11293
|
+
await pageService.savePage(path5, pageData);
|
|
10281
11294
|
pageRefs++;
|
|
10282
11295
|
}
|
|
10283
11296
|
}
|
|
@@ -10435,12 +11448,12 @@ var CMSService = class {
|
|
|
10435
11448
|
* @param locale - Optional locale for i18n slug matching
|
|
10436
11449
|
* @returns CMSRouteMatch if matched, null otherwise
|
|
10437
11450
|
*/
|
|
10438
|
-
async matchRoute(
|
|
11451
|
+
async matchRoute(path5, locale) {
|
|
10439
11452
|
if (!this.provider) {
|
|
10440
11453
|
return null;
|
|
10441
11454
|
}
|
|
10442
11455
|
for (const route of this.routePatterns) {
|
|
10443
|
-
const match =
|
|
11456
|
+
const match = path5.match(route.regex);
|
|
10444
11457
|
if (match) {
|
|
10445
11458
|
const urlSlug = match[route.slugGroup];
|
|
10446
11459
|
if (urlSlug === void 0) continue;
|
|
@@ -10549,11 +11562,11 @@ var CMSService = class {
|
|
|
10549
11562
|
if (options?.excludeDrafts && isItemDraftForLocale(item, locale)) continue;
|
|
10550
11563
|
const localizedSlug = slugValue[locale];
|
|
10551
11564
|
if (localizedSlug) {
|
|
10552
|
-
const
|
|
11565
|
+
const path5 = urlPattern.replace("{{slug}}", localizedSlug);
|
|
10553
11566
|
if (locale === defaultLocale) {
|
|
10554
|
-
urls.push(
|
|
11567
|
+
urls.push(path5);
|
|
10555
11568
|
} else {
|
|
10556
|
-
urls.push(`/${locale}${
|
|
11569
|
+
urls.push(`/${locale}${path5}`);
|
|
10557
11570
|
}
|
|
10558
11571
|
}
|
|
10559
11572
|
}
|
|
@@ -10805,7 +11818,7 @@ var CMSService = class {
|
|
|
10805
11818
|
};
|
|
10806
11819
|
|
|
10807
11820
|
// lib/server/providers/fileSystemCMSProvider.ts
|
|
10808
|
-
import { existsSync as
|
|
11821
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
10809
11822
|
import { join as join6 } from "node:path";
|
|
10810
11823
|
var log6 = createLogger("FileSystemCMSProvider");
|
|
10811
11824
|
var DRAFT_FILE_SUFFIX = `${CMS_DRAFT_SUFFIX}.json`;
|
|
@@ -10878,10 +11891,10 @@ var FileSystemCMSProvider = class {
|
|
|
10878
11891
|
return this.schemaCache;
|
|
10879
11892
|
}
|
|
10880
11893
|
const schemas = /* @__PURE__ */ new Map();
|
|
10881
|
-
if (!
|
|
11894
|
+
if (!existsSync8(this.templatesDir)) {
|
|
10882
11895
|
return schemas;
|
|
10883
11896
|
}
|
|
10884
|
-
const files =
|
|
11897
|
+
const files = readdirSync5(this.templatesDir);
|
|
10885
11898
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
10886
11899
|
const results = await Promise.all(
|
|
10887
11900
|
jsonFiles.map(async (file) => {
|
|
@@ -10911,10 +11924,10 @@ var FileSystemCMSProvider = class {
|
|
|
10911
11924
|
async getItems(collection) {
|
|
10912
11925
|
this.validateCollection(collection);
|
|
10913
11926
|
const collectionDir = join6(this.cmsDir, collection);
|
|
10914
|
-
if (!
|
|
11927
|
+
if (!existsSync8(collectionDir)) {
|
|
10915
11928
|
return [];
|
|
10916
11929
|
}
|
|
10917
|
-
const files =
|
|
11930
|
+
const files = readdirSync5(collectionDir);
|
|
10918
11931
|
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.endsWith(DRAFT_FILE_SUFFIX));
|
|
10919
11932
|
const results = await Promise.all(
|
|
10920
11933
|
jsonFiles.map(async (file) => {
|
|
@@ -11004,7 +12017,7 @@ var FileSystemCMSProvider = class {
|
|
|
11004
12017
|
}
|
|
11005
12018
|
this.validateFilename(filename);
|
|
11006
12019
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11007
|
-
if (!
|
|
12020
|
+
if (!existsSync8(collectionDir)) {
|
|
11008
12021
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11009
12022
|
}
|
|
11010
12023
|
const itemData = stripTransient(item);
|
|
@@ -11019,11 +12032,11 @@ var FileSystemCMSProvider = class {
|
|
|
11019
12032
|
this.validateFilename(filename);
|
|
11020
12033
|
const { unlink } = await import("node:fs/promises");
|
|
11021
12034
|
const publishedPath = join6(this.cmsDir, collection, `${filename}.json`);
|
|
11022
|
-
if (
|
|
12035
|
+
if (existsSync8(publishedPath)) {
|
|
11023
12036
|
await unlink(publishedPath);
|
|
11024
12037
|
}
|
|
11025
12038
|
const draftPath = this.draftPath(collection, filename);
|
|
11026
|
-
if (
|
|
12039
|
+
if (existsSync8(draftPath)) {
|
|
11027
12040
|
await unlink(draftPath);
|
|
11028
12041
|
}
|
|
11029
12042
|
}
|
|
@@ -11057,8 +12070,8 @@ var FileSystemCMSProvider = class {
|
|
|
11057
12070
|
async getAllDrafts(collection) {
|
|
11058
12071
|
this.validateCollection(collection);
|
|
11059
12072
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11060
|
-
if (!
|
|
11061
|
-
const files =
|
|
12073
|
+
if (!existsSync8(collectionDir)) return [];
|
|
12074
|
+
const files = readdirSync5(collectionDir).filter((f) => f.endsWith(DRAFT_FILE_SUFFIX));
|
|
11062
12075
|
const results = await Promise.all(
|
|
11063
12076
|
files.map(async (file) => {
|
|
11064
12077
|
const filePath = join6(collectionDir, file);
|
|
@@ -11083,7 +12096,7 @@ var FileSystemCMSProvider = class {
|
|
|
11083
12096
|
async hasDraft(collection, filename) {
|
|
11084
12097
|
this.validateCollection(collection);
|
|
11085
12098
|
this.validateFilename(filename);
|
|
11086
|
-
return
|
|
12099
|
+
return existsSync8(this.draftPath(collection, filename));
|
|
11087
12100
|
}
|
|
11088
12101
|
/**
|
|
11089
12102
|
* Save the draft version of an item. Loose validation — drafts may have
|
|
@@ -11118,7 +12131,7 @@ var FileSystemCMSProvider = class {
|
|
|
11118
12131
|
}
|
|
11119
12132
|
this.validateFilename(filename);
|
|
11120
12133
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11121
|
-
if (!
|
|
12134
|
+
if (!existsSync8(collectionDir)) {
|
|
11122
12135
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11123
12136
|
}
|
|
11124
12137
|
const itemData = stripTransient(item);
|
|
@@ -11138,7 +12151,7 @@ var FileSystemCMSProvider = class {
|
|
|
11138
12151
|
this.validateFilename(filename);
|
|
11139
12152
|
const { unlink } = await import("node:fs/promises");
|
|
11140
12153
|
const filePath = this.draftPath(collection, filename);
|
|
11141
|
-
if (
|
|
12154
|
+
if (existsSync8(filePath)) {
|
|
11142
12155
|
await unlink(filePath);
|
|
11143
12156
|
}
|
|
11144
12157
|
}
|
|
@@ -11167,13 +12180,13 @@ var FileSystemCMSProvider = class {
|
|
|
11167
12180
|
throw new Error(`Cannot publish invalid draft: ${messages}`);
|
|
11168
12181
|
}
|
|
11169
12182
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11170
|
-
if (!
|
|
12183
|
+
if (!existsSync8(collectionDir)) {
|
|
11171
12184
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11172
12185
|
}
|
|
11173
12186
|
const itemData = stripTransient(validation.data);
|
|
11174
12187
|
const publishedPath = join6(collectionDir, `${filename}.json`);
|
|
11175
12188
|
await writeFile3(publishedPath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
11176
|
-
if (
|
|
12189
|
+
if (existsSync8(draftFilePath)) {
|
|
11177
12190
|
await unlink(draftFilePath);
|
|
11178
12191
|
}
|
|
11179
12192
|
return normalizeItem(itemData, filename);
|
|
@@ -11194,18 +12207,18 @@ var FileSystemCMSProvider = class {
|
|
|
11194
12207
|
);
|
|
11195
12208
|
}
|
|
11196
12209
|
const { writeFile: writeFile3, mkdir } = await import("node:fs/promises");
|
|
11197
|
-
if (!
|
|
12210
|
+
if (!existsSync8(this.templatesDir)) {
|
|
11198
12211
|
await mkdir(this.templatesDir, { recursive: true });
|
|
11199
12212
|
}
|
|
11200
12213
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11201
|
-
if (
|
|
12214
|
+
if (existsSync8(pageFilePath)) {
|
|
11202
12215
|
throw new Error(`Page file already exists: templates/${collectionId}.json`);
|
|
11203
12216
|
}
|
|
11204
12217
|
await writeFile3(pageFilePath, JSON.stringify(pageData, null, 2), "utf-8");
|
|
11205
12218
|
const schemaSource = pageData?.meta?.cms?.source;
|
|
11206
12219
|
if (schemaSource !== "sanity") {
|
|
11207
12220
|
const collectionDir = join6(this.cmsDir, collectionId);
|
|
11208
|
-
if (!
|
|
12221
|
+
if (!existsSync8(collectionDir)) {
|
|
11209
12222
|
await mkdir(collectionDir, { recursive: true });
|
|
11210
12223
|
}
|
|
11211
12224
|
}
|
|
@@ -11221,12 +12234,12 @@ var FileSystemCMSProvider = class {
|
|
|
11221
12234
|
`Invalid collection ID: "${collectionId}". Collection IDs must contain only letters, numbers, hyphens, and underscores.`
|
|
11222
12235
|
);
|
|
11223
12236
|
}
|
|
11224
|
-
const { readFile:
|
|
12237
|
+
const { readFile: readFile3, writeFile: writeFile3 } = await import("node:fs/promises");
|
|
11225
12238
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11226
|
-
if (!
|
|
12239
|
+
if (!existsSync8(pageFilePath)) {
|
|
11227
12240
|
throw new Error(`Collection not found: ${collectionId}`);
|
|
11228
12241
|
}
|
|
11229
|
-
const content = await
|
|
12242
|
+
const content = await readFile3(pageFilePath, "utf-8");
|
|
11230
12243
|
const pageData = JSON.parse(content);
|
|
11231
12244
|
pageData.meta.cms = {
|
|
11232
12245
|
...pageData.meta.cms,
|
|
@@ -11245,26 +12258,26 @@ var PageCache = class {
|
|
|
11245
12258
|
/**
|
|
11246
12259
|
* Store page content and line map in cache
|
|
11247
12260
|
*/
|
|
11248
|
-
set(
|
|
11249
|
-
this.cache.set(
|
|
12261
|
+
set(path5, content, lineMap) {
|
|
12262
|
+
this.cache.set(path5, { content, lineMap });
|
|
11250
12263
|
}
|
|
11251
12264
|
/**
|
|
11252
12265
|
* Get cached page data
|
|
11253
12266
|
*/
|
|
11254
|
-
get(
|
|
11255
|
-
return this.cache.get(
|
|
12267
|
+
get(path5) {
|
|
12268
|
+
return this.cache.get(path5);
|
|
11256
12269
|
}
|
|
11257
12270
|
/**
|
|
11258
12271
|
* Get page content from cache
|
|
11259
12272
|
*/
|
|
11260
|
-
getContent(
|
|
11261
|
-
return this.cache.get(
|
|
12273
|
+
getContent(path5) {
|
|
12274
|
+
return this.cache.get(path5)?.content;
|
|
11262
12275
|
}
|
|
11263
12276
|
/**
|
|
11264
12277
|
* Get line map from cache
|
|
11265
12278
|
*/
|
|
11266
|
-
getLineMap(
|
|
11267
|
-
return this.cache.get(
|
|
12279
|
+
getLineMap(path5) {
|
|
12280
|
+
return this.cache.get(path5)?.lineMap;
|
|
11268
12281
|
}
|
|
11269
12282
|
/**
|
|
11270
12283
|
* Get line range for a specific element path
|
|
@@ -11275,14 +12288,14 @@ var PageCache = class {
|
|
|
11275
12288
|
/**
|
|
11276
12289
|
* Delete page from cache
|
|
11277
12290
|
*/
|
|
11278
|
-
delete(
|
|
11279
|
-
this.cache.delete(
|
|
12291
|
+
delete(path5) {
|
|
12292
|
+
this.cache.delete(path5);
|
|
11280
12293
|
}
|
|
11281
12294
|
/**
|
|
11282
12295
|
* Check if page exists in cache
|
|
11283
12296
|
*/
|
|
11284
|
-
has(
|
|
11285
|
-
return this.cache.has(
|
|
12297
|
+
has(path5) {
|
|
12298
|
+
return this.cache.has(path5);
|
|
11286
12299
|
}
|
|
11287
12300
|
/**
|
|
11288
12301
|
* Get all page paths
|
|
@@ -11321,21 +12334,21 @@ var DraftPageStore = class {
|
|
|
11321
12334
|
* Store a draft for a page path. The value is the raw JSON string the
|
|
11322
12335
|
* SSR pipeline expects, matching PageService.getPage()'s contract.
|
|
11323
12336
|
*/
|
|
11324
|
-
set(
|
|
11325
|
-
this.drafts.set(
|
|
12337
|
+
set(path5, content) {
|
|
12338
|
+
this.drafts.set(path5, content);
|
|
11326
12339
|
}
|
|
11327
12340
|
/**
|
|
11328
12341
|
* Get the current draft string for a page path, if any.
|
|
11329
12342
|
*/
|
|
11330
|
-
get(
|
|
11331
|
-
return this.drafts.get(
|
|
12343
|
+
get(path5) {
|
|
12344
|
+
return this.drafts.get(path5);
|
|
11332
12345
|
}
|
|
11333
12346
|
/**
|
|
11334
12347
|
* Drop the draft for a specific path. Called when the page is saved to
|
|
11335
12348
|
* disk so subsequent renders read the persisted version.
|
|
11336
12349
|
*/
|
|
11337
|
-
clear(
|
|
11338
|
-
this.drafts.delete(
|
|
12350
|
+
clear(path5) {
|
|
12351
|
+
this.drafts.delete(path5);
|
|
11339
12352
|
}
|
|
11340
12353
|
/**
|
|
11341
12354
|
* Drop every draft. Used on shutdown / project switch.
|
|
@@ -11343,14 +12356,19 @@ var DraftPageStore = class {
|
|
|
11343
12356
|
clearAll() {
|
|
11344
12357
|
this.drafts.clear();
|
|
11345
12358
|
}
|
|
11346
|
-
has(
|
|
11347
|
-
return this.drafts.has(
|
|
12359
|
+
has(path5) {
|
|
12360
|
+
return this.drafts.has(path5);
|
|
11348
12361
|
}
|
|
11349
12362
|
};
|
|
11350
12363
|
|
|
11351
12364
|
// lib/server/fileWatcher.ts
|
|
11352
|
-
import { watch, existsSync as
|
|
11353
|
-
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
|
|
11354
12372
|
function watchRecursive(root, listener) {
|
|
11355
12373
|
const nested = [];
|
|
11356
12374
|
const watchedDirs = /* @__PURE__ */ new Set();
|
|
@@ -11358,17 +12376,17 @@ function watchRecursive(root, listener) {
|
|
|
11358
12376
|
const coverNewDir = (abs) => {
|
|
11359
12377
|
let isDir = false;
|
|
11360
12378
|
try {
|
|
11361
|
-
isDir =
|
|
12379
|
+
isDir = statSync2(abs).isDirectory();
|
|
11362
12380
|
} catch {
|
|
11363
12381
|
return;
|
|
11364
12382
|
}
|
|
11365
12383
|
if (!isDir || watchedDirs.has(abs)) return;
|
|
11366
12384
|
attachDir(abs);
|
|
11367
12385
|
try {
|
|
11368
|
-
for (const e of
|
|
12386
|
+
for (const e of readdirSync6(abs, { withFileTypes: true })) {
|
|
11369
12387
|
const child = join7(abs, e.name);
|
|
11370
12388
|
if (e.isDirectory()) coverNewDir(child);
|
|
11371
|
-
else listener("rename",
|
|
12389
|
+
else listener("rename", relative2(root, child));
|
|
11372
12390
|
}
|
|
11373
12391
|
} catch {
|
|
11374
12392
|
}
|
|
@@ -11381,7 +12399,7 @@ function watchRecursive(root, listener) {
|
|
|
11381
12399
|
w = watch(absDir, { recursive: true }, (event, filename) => {
|
|
11382
12400
|
if (!filename) return;
|
|
11383
12401
|
const abs = join7(absDir, filename);
|
|
11384
|
-
listener(event,
|
|
12402
|
+
listener(event, relative2(root, abs));
|
|
11385
12403
|
coverNewDir(abs);
|
|
11386
12404
|
});
|
|
11387
12405
|
} catch {
|
|
@@ -11406,19 +12424,19 @@ function watchRecursive(root, listener) {
|
|
|
11406
12424
|
return handle;
|
|
11407
12425
|
}
|
|
11408
12426
|
function attachWhenDirExists(dirPath, attach, setWatcher) {
|
|
11409
|
-
if (
|
|
12427
|
+
if (existsSync9(dirPath)) {
|
|
11410
12428
|
setWatcher(attach());
|
|
11411
12429
|
return;
|
|
11412
12430
|
}
|
|
11413
12431
|
const parentDir = dirname2(dirPath);
|
|
11414
12432
|
const targetName = basename2(dirPath);
|
|
11415
|
-
if (!
|
|
12433
|
+
if (!existsSync9(parentDir)) {
|
|
11416
12434
|
return;
|
|
11417
12435
|
}
|
|
11418
12436
|
let parentWatcher = null;
|
|
11419
12437
|
parentWatcher = watch(parentDir, (_event, filename) => {
|
|
11420
12438
|
if (filename !== targetName) return;
|
|
11421
|
-
if (!
|
|
12439
|
+
if (!existsSync9(dirPath)) return;
|
|
11422
12440
|
parentWatcher?.close();
|
|
11423
12441
|
parentWatcher = null;
|
|
11424
12442
|
setWatcher(attach());
|
|
@@ -11448,7 +12466,7 @@ var FileWatcher = class {
|
|
|
11448
12466
|
* Watches both .json and .js files to detect component definition and JavaScript changes
|
|
11449
12467
|
*/
|
|
11450
12468
|
watchComponents(dirPath = projectPaths.components()) {
|
|
11451
|
-
if (!
|
|
12469
|
+
if (!existsSync9(dirPath)) {
|
|
11452
12470
|
return;
|
|
11453
12471
|
}
|
|
11454
12472
|
this.componentsWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11464,7 +12482,7 @@ var FileWatcher = class {
|
|
|
11464
12482
|
* Start watching pages directory
|
|
11465
12483
|
*/
|
|
11466
12484
|
watchPages(dirPath = projectPaths.pages()) {
|
|
11467
|
-
if (!
|
|
12485
|
+
if (!existsSync9(dirPath)) {
|
|
11468
12486
|
return;
|
|
11469
12487
|
}
|
|
11470
12488
|
this.pagesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11504,7 +12522,7 @@ var FileWatcher = class {
|
|
|
11504
12522
|
* Start watching colors.json file
|
|
11505
12523
|
*/
|
|
11506
12524
|
watchColors(filePath = projectPaths.colors()) {
|
|
11507
|
-
if (!
|
|
12525
|
+
if (!existsSync9(filePath)) {
|
|
11508
12526
|
return;
|
|
11509
12527
|
}
|
|
11510
12528
|
const dirPath = getProjectRoot();
|
|
@@ -11561,7 +12579,7 @@ var FileWatcher = class {
|
|
|
11561
12579
|
* Start watching images directory for new source images
|
|
11562
12580
|
*/
|
|
11563
12581
|
watchImages(dirPath = projectPaths.images()) {
|
|
11564
|
-
if (!
|
|
12582
|
+
if (!existsSync9(dirPath)) {
|
|
11565
12583
|
return;
|
|
11566
12584
|
}
|
|
11567
12585
|
this.imagesWatcher = watch(dirPath, { recursive: false }, async (event, filename) => {
|
|
@@ -11588,7 +12606,7 @@ var FileWatcher = class {
|
|
|
11588
12606
|
* Start watching libraries directory for CSS/JS changes
|
|
11589
12607
|
*/
|
|
11590
12608
|
watchLibraries(dirPath = projectPaths.libraries()) {
|
|
11591
|
-
if (!
|
|
12609
|
+
if (!existsSync9(dirPath)) {
|
|
11592
12610
|
return;
|
|
11593
12611
|
}
|
|
11594
12612
|
this.librariesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11621,7 +12639,8 @@ var FileWatcher = class {
|
|
|
11621
12639
|
if (this.callbacks.onCMSTemplateChange) await this.callbacks.onCMSTemplateChange();
|
|
11622
12640
|
return;
|
|
11623
12641
|
}
|
|
11624
|
-
const
|
|
12642
|
+
const pageName = collapseFolderIndex(filename.replace(/\.astro$/, ""));
|
|
12643
|
+
const pagePath = mapPageNameToPath(pageName);
|
|
11625
12644
|
if (this.callbacks.onPageChange) await this.callbacks.onPageChange(pagePath);
|
|
11626
12645
|
}),
|
|
11627
12646
|
(w) => {
|
|
@@ -11789,9 +12808,9 @@ var FileWatcherService = class {
|
|
|
11789
12808
|
this.refreshSchemasTimer = null;
|
|
11790
12809
|
await cmsService.refreshSchemas();
|
|
11791
12810
|
const ids = new Set(cmsService.getAllSchemas().keys());
|
|
11792
|
-
for (const
|
|
11793
|
-
if (
|
|
11794
|
-
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);
|
|
11795
12814
|
}
|
|
11796
12815
|
}
|
|
11797
12816
|
for (const id of ids) {
|
|
@@ -11845,20 +12864,20 @@ var FileWatcherService = class {
|
|
|
11845
12864
|
};
|
|
11846
12865
|
|
|
11847
12866
|
// lib/server/migrateTemplates.ts
|
|
11848
|
-
import { existsSync as
|
|
12867
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
11849
12868
|
import { rename } from "node:fs/promises";
|
|
11850
12869
|
import { join as join8 } from "node:path";
|
|
11851
12870
|
async function migrateTemplatesDirectory() {
|
|
11852
12871
|
const oldDir = join8(projectPaths.pages(), "templates");
|
|
11853
12872
|
const newDir = projectPaths.templates();
|
|
11854
|
-
if (!
|
|
11855
|
-
if (
|
|
12873
|
+
if (!existsSync10(oldDir)) return;
|
|
12874
|
+
if (existsSync10(newDir)) return;
|
|
11856
12875
|
await rename(oldDir, newDir);
|
|
11857
12876
|
console.log("Migrated CMS templates: pages/templates/ \u2192 templates/");
|
|
11858
12877
|
}
|
|
11859
12878
|
|
|
11860
12879
|
// build-astro.ts
|
|
11861
|
-
import { existsSync as
|
|
12880
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync4, rmSync, statSync as statSync3, copyFileSync, writeFileSync } from "node:fs";
|
|
11862
12881
|
import { writeFile as writeFile2, readFile } from "node:fs/promises";
|
|
11863
12882
|
import { join as join9 } from "node:path";
|
|
11864
12883
|
import { createHash } from "node:crypto";
|
|
@@ -12944,8 +13963,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12944
13963
|
if (dynLiteralMatch) {
|
|
12945
13964
|
const existing = dynLiteralMatch[1] ?? "";
|
|
12946
13965
|
const trimmed = existing.trimEnd();
|
|
12947
|
-
const
|
|
12948
|
-
return ` style={\`${existing}${
|
|
13966
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
13967
|
+
return ` style={\`${existing}${sep2}${extraCss}\`}`;
|
|
12949
13968
|
}
|
|
12950
13969
|
const dynExprMatch = styleAttr.match(/^ style=\{([\s\S]*)\}$/);
|
|
12951
13970
|
if (dynExprMatch) {
|
|
@@ -12953,8 +13972,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12953
13972
|
}
|
|
12954
13973
|
return styleAttr.replace(/style="([^"]*)"/, (_, existing) => {
|
|
12955
13974
|
const trimmed = existing.trimEnd();
|
|
12956
|
-
const
|
|
12957
|
-
return `style="${existing}${
|
|
13975
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
13976
|
+
return `style="${existing}${sep2}${extraCss}"`;
|
|
12958
13977
|
});
|
|
12959
13978
|
}
|
|
12960
13979
|
var PICTURE_WIDTHS = [500, 800, 1080, 1600, 2400];
|
|
@@ -13680,7 +14699,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13680
14699
|
const code = localeConfig.code;
|
|
13681
14700
|
const isCurrent = code === currentLocale;
|
|
13682
14701
|
if (!showCurrent && isCurrent) continue;
|
|
13683
|
-
const
|
|
14702
|
+
const path5 = slugMap[code] || "/";
|
|
13684
14703
|
const classes = isCurrent ? activeItemClasses : itemClasses;
|
|
13685
14704
|
const classAttr = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
13686
14705
|
const currentAttr = isCurrent ? ' data-current="true"' : ' data-current="false"';
|
|
@@ -13705,7 +14724,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13705
14724
|
}
|
|
13706
14725
|
linkContent += `<div>${escapeJSX(displayText)}</div>`;
|
|
13707
14726
|
links.push(
|
|
13708
|
-
`${ind(ctx)} <a href="${escapeJSX(
|
|
14727
|
+
`${ind(ctx)} <a href="${escapeJSX(path5)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`
|
|
13709
14728
|
);
|
|
13710
14729
|
}
|
|
13711
14730
|
let linksContent;
|
|
@@ -14199,8 +15218,8 @@ function emitAstroPage(options) {
|
|
|
14199
15218
|
}
|
|
14200
15219
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14201
15220
|
for (const comp of componentImports) {
|
|
14202
|
-
const
|
|
14203
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15221
|
+
const path5 = componentImportPath(fileDepth, comp);
|
|
15222
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14204
15223
|
}
|
|
14205
15224
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
14206
15225
|
const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral2(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral2(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral2(libraryTags.bodyEndJS || "")}\` }`;
|
|
@@ -14539,8 +15558,8 @@ function emitCMSPage(options) {
|
|
|
14539
15558
|
}
|
|
14540
15559
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14541
15560
|
for (const comp of componentImports) {
|
|
14542
|
-
const
|
|
14543
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15561
|
+
const path5 = componentImportPath2(fileDepth, comp);
|
|
15562
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14544
15563
|
}
|
|
14545
15564
|
const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
|
|
14546
15565
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
@@ -14713,24 +15732,24 @@ function writePageScript(javascript, scriptsDir) {
|
|
|
14713
15732
|
if (!javascript) return [];
|
|
14714
15733
|
const hash = hashContent3(javascript);
|
|
14715
15734
|
const scriptFile = `${hash}.js`;
|
|
14716
|
-
if (!
|
|
15735
|
+
if (!existsSync11(scriptsDir)) {
|
|
14717
15736
|
mkdirSync4(scriptsDir, { recursive: true });
|
|
14718
15737
|
}
|
|
14719
15738
|
const fullScriptPath = join9(scriptsDir, scriptFile);
|
|
14720
|
-
if (!
|
|
15739
|
+
if (!existsSync11(fullScriptPath)) {
|
|
14721
15740
|
writeFileSync(fullScriptPath, javascript, "utf-8");
|
|
14722
15741
|
}
|
|
14723
15742
|
return [`/_scripts/${scriptFile}`];
|
|
14724
15743
|
}
|
|
14725
15744
|
function copyDirectory(src, dest, filter) {
|
|
14726
|
-
if (!
|
|
14727
|
-
if (!
|
|
14728
|
-
const files =
|
|
15745
|
+
if (!existsSync11(src)) return;
|
|
15746
|
+
if (!existsSync11(dest)) mkdirSync4(dest, { recursive: true });
|
|
15747
|
+
const files = readdirSync7(src);
|
|
14729
15748
|
for (const file of files) {
|
|
14730
15749
|
if (filter && !filter(file)) continue;
|
|
14731
15750
|
const srcPath = join9(src, file);
|
|
14732
15751
|
const destPath = join9(dest, file);
|
|
14733
|
-
const stat =
|
|
15752
|
+
const stat = statSync3(srcPath);
|
|
14734
15753
|
if (stat.isDirectory()) copyDirectory(srcPath, destPath, filter);
|
|
14735
15754
|
else copyFileSync(srcPath, destPath);
|
|
14736
15755
|
}
|
|
@@ -14746,8 +15765,8 @@ function isCMSPage(pageData) {
|
|
|
14746
15765
|
}
|
|
14747
15766
|
function scanJSONFiles(dir, prefix = "") {
|
|
14748
15767
|
const results = [];
|
|
14749
|
-
if (!
|
|
14750
|
-
const entries =
|
|
15768
|
+
if (!existsSync11(dir)) return results;
|
|
15769
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14751
15770
|
for (const entry of entries) {
|
|
14752
15771
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
14753
15772
|
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
@@ -14919,7 +15938,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14919
15938
|
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
14920
15939
|
const imageMetadataMap = await buildImageMetadataMap();
|
|
14921
15940
|
const outDir = outputDir || join9(projectPaths.project, "astro-export");
|
|
14922
|
-
if (
|
|
15941
|
+
if (existsSync11(outDir)) {
|
|
14923
15942
|
rmSync(outDir, { recursive: true, force: true });
|
|
14924
15943
|
}
|
|
14925
15944
|
mkdirSync4(outDir, { recursive: true });
|
|
@@ -14934,7 +15953,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14934
15953
|
mkdirSync4(d, { recursive: true });
|
|
14935
15954
|
}
|
|
14936
15955
|
const pagesDir = projectPaths.pages();
|
|
14937
|
-
if (!
|
|
15956
|
+
if (!existsSync11(pagesDir)) {
|
|
14938
15957
|
console.error("Pages directory not found!");
|
|
14939
15958
|
return { pages: 0, cmsPages: 0, collections: 0, errors: 1 };
|
|
14940
15959
|
}
|
|
@@ -15065,7 +16084,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15065
16084
|
const shouldInline = css.inline !== false;
|
|
15066
16085
|
const relPath = css.url.slice(1);
|
|
15067
16086
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15068
|
-
if (!
|
|
16087
|
+
if (!existsSync11(srcPath)) continue;
|
|
15069
16088
|
if (shouldInline) {
|
|
15070
16089
|
try {
|
|
15071
16090
|
inlineContents.set(css.url, await readFile(srcPath, "utf-8"));
|
|
@@ -15079,7 +16098,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15079
16098
|
for (const js of buildLibraries.js || []) {
|
|
15080
16099
|
if (js.url.startsWith("/")) {
|
|
15081
16100
|
const relPath = js.url.slice(1);
|
|
15082
|
-
if (
|
|
16101
|
+
if (existsSync11(join9(projectPaths.project, relPath))) {
|
|
15083
16102
|
localLibsToCopy.push(relPath);
|
|
15084
16103
|
}
|
|
15085
16104
|
}
|
|
@@ -15100,8 +16119,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15100
16119
|
const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
|
|
15101
16120
|
const collectionUrlExpr = /* @__PURE__ */ new Map();
|
|
15102
16121
|
const mergedRichTextFields = /* @__PURE__ */ new Set();
|
|
15103
|
-
if (
|
|
15104
|
-
for (const file of
|
|
16122
|
+
if (existsSync11(templatesDir)) {
|
|
16123
|
+
for (const file of readdirSync7(templatesDir).filter((f) => f.endsWith(".json"))) {
|
|
15105
16124
|
const tc = await loadJSONFile(join9(templatesDir, file));
|
|
15106
16125
|
if (!tc) continue;
|
|
15107
16126
|
try {
|
|
@@ -15116,8 +16135,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15116
16135
|
}
|
|
15117
16136
|
}
|
|
15118
16137
|
}
|
|
15119
|
-
if (
|
|
15120
|
-
const templateFiles =
|
|
16138
|
+
if (existsSync11(templatesDir)) {
|
|
16139
|
+
const templateFiles = readdirSync7(templatesDir).filter((f) => f.endsWith(".json"));
|
|
15121
16140
|
for (const file of templateFiles) {
|
|
15122
16141
|
const templateContent = await loadJSONFile(join9(templatesDir, file));
|
|
15123
16142
|
if (!templateContent) continue;
|
|
@@ -15210,7 +16229,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15210
16229
|
});
|
|
15211
16230
|
const astroFileFull = join9(pagesOutDir, astroFilePath);
|
|
15212
16231
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15213
|
-
if (!
|
|
16232
|
+
if (!existsSync11(astroFileDir)) {
|
|
15214
16233
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15215
16234
|
}
|
|
15216
16235
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15361,7 +16380,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
15361
16380
|
}
|
|
15362
16381
|
const astroFileFull = join9(pagesOutDir, result.astroFilePath);
|
|
15363
16382
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15364
|
-
if (!
|
|
16383
|
+
if (!existsSync11(astroFileDir)) {
|
|
15365
16384
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15366
16385
|
}
|
|
15367
16386
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15393,9 +16412,9 @@ export const GET: APIRoute = () => {
|
|
|
15393
16412
|
mkdirSync4(collectionDir, { recursive: true });
|
|
15394
16413
|
const richTextFieldNames = Object.entries(schema.fields || {}).filter(([, fd]) => fd.type === "rich-text").map(([fn]) => fn);
|
|
15395
16414
|
const cmsItemsDir = join9(projectPaths.cms(), schema.id);
|
|
15396
|
-
if (
|
|
16415
|
+
if (existsSync11(cmsItemsDir)) {
|
|
15397
16416
|
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
15398
|
-
const itemFiles =
|
|
16417
|
+
const itemFiles = readdirSync7(cmsItemsDir).filter(
|
|
15399
16418
|
(f) => f.endsWith(".json") && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`))
|
|
15400
16419
|
);
|
|
15401
16420
|
for (const itemFile of itemFiles) {
|
|
@@ -15442,26 +16461,26 @@ export { collections };
|
|
|
15442
16461
|
await writeFile2(join9(srcDir, "content.config.ts"), configContent, "utf-8");
|
|
15443
16462
|
}
|
|
15444
16463
|
const imagesSrcDir = join9(projectPaths.project, "images");
|
|
15445
|
-
if (
|
|
16464
|
+
if (existsSync11(imagesSrcDir)) {
|
|
15446
16465
|
copyDirectory(imagesSrcDir, join9(srcDir, "assets", "images"), shouldCopyImageForAstro);
|
|
15447
16466
|
copyDirectory(imagesSrcDir, join9(publicDir, "images"));
|
|
15448
16467
|
}
|
|
15449
16468
|
const publicAssetDirs = ["fonts", "icons", "videos", "assets"];
|
|
15450
16469
|
for (const dir of publicAssetDirs) {
|
|
15451
16470
|
const srcAssetDir = join9(projectPaths.project, dir);
|
|
15452
|
-
if (
|
|
16471
|
+
if (existsSync11(srcAssetDir)) {
|
|
15453
16472
|
copyDirectory(srcAssetDir, join9(publicDir, dir));
|
|
15454
16473
|
}
|
|
15455
16474
|
}
|
|
15456
16475
|
const librariesDir = join9(projectPaths.project, "libraries");
|
|
15457
|
-
if (
|
|
16476
|
+
if (existsSync11(librariesDir)) {
|
|
15458
16477
|
copyDirectory(librariesDir, join9(publicDir, "libraries"));
|
|
15459
16478
|
}
|
|
15460
16479
|
for (const relPath of localLibsToCopy) {
|
|
15461
16480
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15462
16481
|
const destPath = join9(publicDir, relPath);
|
|
15463
16482
|
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
|
|
15464
|
-
if (destDir && !
|
|
16483
|
+
if (destDir && !existsSync11(destDir)) mkdirSync4(destDir, { recursive: true });
|
|
15465
16484
|
copyFileSync(srcPath, destPath);
|
|
15466
16485
|
}
|
|
15467
16486
|
const packageJson = {
|
|
@@ -15531,6 +16550,32 @@ export default defineConfig({${siteUrl ? `
|
|
|
15531
16550
|
|
|
15532
16551
|
// lib/server/index.ts
|
|
15533
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
|
+
}
|
|
15534
16579
|
export {
|
|
15535
16580
|
CMSService,
|
|
15536
16581
|
ColorService,
|
|
@@ -15550,6 +16595,7 @@ export {
|
|
|
15550
16595
|
SERVE_PORT,
|
|
15551
16596
|
VariableService,
|
|
15552
16597
|
WebSocketManager,
|
|
16598
|
+
auditStylesheet,
|
|
15553
16599
|
buildAstroProject,
|
|
15554
16600
|
buildAttributes,
|
|
15555
16601
|
buildComponentHTML,
|
|
@@ -15557,6 +16603,7 @@ export {
|
|
|
15557
16603
|
buildLineMap,
|
|
15558
16604
|
bundleFile,
|
|
15559
16605
|
clearJSValidationCache,
|
|
16606
|
+
collectClassesFromPageData,
|
|
15560
16607
|
collectComponentCSS,
|
|
15561
16608
|
collectComponentJavaScript,
|
|
15562
16609
|
colorService,
|
|
@@ -15571,6 +16618,7 @@ export {
|
|
|
15571
16618
|
errorResponse,
|
|
15572
16619
|
escapeHtml,
|
|
15573
16620
|
extractPageMeta,
|
|
16621
|
+
extractRules,
|
|
15574
16622
|
fileExists,
|
|
15575
16623
|
formHandlerScript,
|
|
15576
16624
|
generateBuildErrorPage,
|
|
@@ -15617,12 +16665,14 @@ export {
|
|
|
15617
16665
|
readJsonFile,
|
|
15618
16666
|
readTextFile,
|
|
15619
16667
|
regenerateThemeCss,
|
|
16668
|
+
renderAuditMarkdown,
|
|
15620
16669
|
renderPageSSR,
|
|
15621
16670
|
resetFontConfig,
|
|
15622
16671
|
resolvePackagePath,
|
|
15623
16672
|
resolveProjectPath,
|
|
15624
16673
|
saveColors,
|
|
15625
16674
|
saveVariables,
|
|
16675
|
+
sectionPrefix,
|
|
15626
16676
|
serializeThemeCss,
|
|
15627
16677
|
serveFile,
|
|
15628
16678
|
setProjectRoot,
|
|
@@ -15634,6 +16684,7 @@ export {
|
|
|
15634
16684
|
watchRecursive,
|
|
15635
16685
|
withErrorHandling,
|
|
15636
16686
|
withLogging,
|
|
16687
|
+
wrapInWebflowTemplate,
|
|
15637
16688
|
writeFile
|
|
15638
16689
|
};
|
|
15639
16690
|
//# sourceMappingURL=index.js.map
|