meno-core 1.1.3 → 1.1.5
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunks/{chunk-3JXK2QFU.js → chunk-H6EOTPJA.js} +2 -2
- package/dist/chunks/{chunk-LOZL5HOF.js → chunk-RLBV2R6J.js} +603 -17
- package/dist/chunks/chunk-RLBV2R6J.js.map +7 -0
- package/dist/chunks/{chunk-FQBIC2OB.js → chunk-YMBUSHII.js} +1 -1
- package/dist/chunks/{chunk-FQBIC2OB.js.map → chunk-YMBUSHII.js.map} +1 -1
- package/dist/lib/client/index.js +46 -28
- package/dist/lib/client/index.js.map +2 -2
- package/dist/lib/server/index.js +1304 -241
- package/dist/lib/server/index.js.map +4 -4
- package/dist/lib/shared/index.js +4 -2
- package/dist/lib/shared/index.js.map +1 -1
- package/lib/client/core/ComponentBuilder.test.ts +32 -0
- package/lib/client/core/ComponentBuilder.ts +30 -0
- package/lib/client/theme.test.ts +2 -2
- package/lib/client/theme.ts +28 -26
- package/lib/server/createServer.ts +5 -1
- package/lib/server/cssAudit.test.ts +270 -0
- package/lib/server/cssAudit.ts +815 -0
- package/lib/server/deMirror.test.ts +61 -0
- package/lib/server/deMirror.ts +317 -0
- package/lib/server/fileWatcher.test.ts +113 -0
- package/lib/server/fileWatcher.ts +42 -1
- package/lib/server/index.ts +19 -0
- package/lib/server/routes/api/core-routes.ts +18 -0
- package/lib/server/routes/api/cssAudit.test.ts +101 -0
- package/lib/server/routes/api/cssAudit.ts +138 -0
- package/lib/server/routes/api/deMirror.test.ts +105 -0
- package/lib/server/routes/api/deMirror.ts +127 -0
- package/lib/server/routes/api/pages.ts +2 -2
- package/lib/server/services/componentService.test.ts +43 -0
- package/lib/server/services/componentService.ts +49 -12
- package/lib/server/services/configService.ts +0 -5
- 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 -1
- package/lib/server/ssr/htmlGenerator.ts +86 -14
- 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 +64 -0
- package/lib/shared/cssGeneration.ts +6 -1
- package/lib/shared/interfaces/contentProvider.ts +9 -0
- package/lib/shared/tailwindThemeScale.ts +1 -0
- package/lib/shared/types/cms.ts +1 -0
- package/lib/shared/types/comment.ts +9 -3
- package/lib/shared/utilityClassConfig.ts +471 -10
- package/lib/shared/utilityClassMapper.test.ts +173 -2
- package/lib/shared/utilityClassMapper.ts +37 -0
- package/lib/shared/utilityClassNames.ts +164 -0
- package/lib/shared/utils/fileUtils.ts +11 -0
- package/lib/shared/validation/schemas.ts +1 -0
- package/lib/shared/viewportUnits.test.ts +34 -1
- package/lib/shared/viewportUnits.ts +43 -0
- package/package.json +3 -1
- package/vite.config.ts +4 -1
- package/dist/chunks/chunk-LOZL5HOF.js.map +0 -7
- /package/dist/chunks/{chunk-3JXK2QFU.js.map → chunk-H6EOTPJA.js.map} +0 -0
package/dist/lib/server/index.js
CHANGED
|
@@ -20,7 +20,7 @@ import {
|
|
|
20
20
|
resolveSlugToPageId,
|
|
21
21
|
rewriteComponentRefs,
|
|
22
22
|
translatePath
|
|
23
|
-
} from "../../chunks/chunk-
|
|
23
|
+
} from "../../chunks/chunk-YMBUSHII.js";
|
|
24
24
|
import {
|
|
25
25
|
syncNetlifyLocale404Block
|
|
26
26
|
} from "../../chunks/chunk-2AR55GYH.js";
|
|
@@ -33,7 +33,7 @@ import {
|
|
|
33
33
|
processStructure,
|
|
34
34
|
resolveHtmlMapping,
|
|
35
35
|
skipEmptyTemplateAttributes
|
|
36
|
-
} from "../../chunks/chunk-
|
|
36
|
+
} from "../../chunks/chunk-H6EOTPJA.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-RLBV2R6J.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
|
/**
|
|
@@ -1093,9 +1094,6 @@ var ConfigService = class {
|
|
|
1093
1094
|
}
|
|
1094
1095
|
return this.config.customCode;
|
|
1095
1096
|
}
|
|
1096
|
-
getShowMenoBadge() {
|
|
1097
|
-
return this.config?.showMenoBadge === true;
|
|
1098
|
-
}
|
|
1099
1097
|
getImageFormat() {
|
|
1100
1098
|
if (this.config?.imageFormat === "avif") return "avif";
|
|
1101
1099
|
return "webp";
|
|
@@ -1518,8 +1516,8 @@ function cachedJsonResponse(req, body, options = {}) {
|
|
|
1518
1516
|
}
|
|
1519
1517
|
|
|
1520
1518
|
// lib/server/routes/api/pages.ts
|
|
1521
|
-
function handlePagesRoute(pageService) {
|
|
1522
|
-
const pages = pageService.getAllPagesWithInfo();
|
|
1519
|
+
async function handlePagesRoute(pageService) {
|
|
1520
|
+
const pages = await pageService.getAllPagesWithInfo();
|
|
1523
1521
|
return jsonResponse({ pages });
|
|
1524
1522
|
}
|
|
1525
1523
|
function handlePageDataRoute(url, pageService) {
|
|
@@ -1641,11 +1639,11 @@ function handleComponentUsageRoute(pageService, componentService) {
|
|
|
1641
1639
|
}
|
|
1642
1640
|
}
|
|
1643
1641
|
const pagePaths = pageService.getAllPagePaths();
|
|
1644
|
-
for (const
|
|
1645
|
-
const pageData = pageService.getPageData(
|
|
1642
|
+
for (const path5 of pagePaths) {
|
|
1643
|
+
const pageData = pageService.getPageData(path5);
|
|
1646
1644
|
if (pageData && "root" in pageData && pageData.root) {
|
|
1647
|
-
const name =
|
|
1648
|
-
walkTree(pageData.root, name,
|
|
1645
|
+
const name = path5 === "/" ? "index" : path5.replace(/^\//, "").replace(/\//g, "-");
|
|
1646
|
+
walkTree(pageData.root, name, path5, "page");
|
|
1649
1647
|
}
|
|
1650
1648
|
}
|
|
1651
1649
|
const allComponents = componentService.getAllComponents();
|
|
@@ -1755,9 +1753,24 @@ function sectionLabelForGroup(group) {
|
|
|
1755
1753
|
if (!group || group === "other") return OTHER_LABEL;
|
|
1756
1754
|
return VARIABLE_GROUPS.find((g) => g.value === group)?.label ?? OTHER_LABEL;
|
|
1757
1755
|
}
|
|
1758
|
-
|
|
1756
|
+
function normalizeSectionLabel(s) {
|
|
1757
|
+
return s.trim().toLowerCase().replace(/[-–—]+/g, " ").replace(/\s+/g, " ").trim();
|
|
1758
|
+
}
|
|
1759
|
+
var NORMALIZED_LABEL_TO_GROUP = new Map(
|
|
1760
|
+
VARIABLE_GROUPS.map((g) => [normalizeSectionLabel(g.label), g.value])
|
|
1761
|
+
);
|
|
1762
|
+
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
1763
|
function groupForSectionLabel(label) {
|
|
1760
|
-
|
|
1764
|
+
const norm = normalizeSectionLabel(label);
|
|
1765
|
+
const exact = NORMALIZED_LABEL_TO_GROUP.get(norm);
|
|
1766
|
+
if (exact) return exact;
|
|
1767
|
+
for (const { norm: labelNorm, group } of PREFIX_LABELS) {
|
|
1768
|
+
if (norm.startsWith(labelNorm)) {
|
|
1769
|
+
const next = norm.charAt(labelNorm.length);
|
|
1770
|
+
if (next === "" || !/[a-z0-9]/.test(next)) return group;
|
|
1771
|
+
}
|
|
1772
|
+
}
|
|
1773
|
+
return null;
|
|
1761
1774
|
}
|
|
1762
1775
|
function isColorValue(value) {
|
|
1763
1776
|
const v = value.trim().toLowerCase();
|
|
@@ -1766,6 +1779,11 @@ function isColorValue(value) {
|
|
|
1766
1779
|
if (v === "transparent" || v === "currentcolor") return true;
|
|
1767
1780
|
return false;
|
|
1768
1781
|
}
|
|
1782
|
+
var NAME_HINT_GROUPS = [
|
|
1783
|
+
{ needle: /(^|[-_])tracking([-_]|$)/, group: "letter-spacing" },
|
|
1784
|
+
{ needle: /(^|[-_])leading([-_]|$)/, group: "line-height" },
|
|
1785
|
+
{ needle: /(^|[-_])(radius|rounded)([-_]|$)/, group: "border-radius" }
|
|
1786
|
+
];
|
|
1769
1787
|
function inferGroup(cssVar, value) {
|
|
1770
1788
|
const v = value.trim().toLowerCase();
|
|
1771
1789
|
if (/^\d+(\.\d+)?m?s$/.test(v)) return "duration";
|
|
@@ -1776,6 +1794,9 @@ function inferGroup(cssVar, value) {
|
|
|
1776
1794
|
}
|
|
1777
1795
|
if (/^\d*\.\d+$/.test(v) || /^[1-9](\.\d+)?$/.test(v)) return "line-height";
|
|
1778
1796
|
const name = cssVar.replace(/^--/, "").toLowerCase();
|
|
1797
|
+
for (const { needle, group } of NAME_HINT_GROUPS) {
|
|
1798
|
+
if (needle.test(name)) return group;
|
|
1799
|
+
}
|
|
1779
1800
|
for (const g of VARIABLE_GROUPS) {
|
|
1780
1801
|
if (g.value !== "other" && name.includes(g.value)) return g.value;
|
|
1781
1802
|
}
|
|
@@ -2057,8 +2078,7 @@ function parseThemeCss(css, breakpoints) {
|
|
|
2057
2078
|
const dn = inner.match(/^colors\s*:\s*([\w-]+)/i);
|
|
2058
2079
|
if (dn) defaultName = dn[1];
|
|
2059
2080
|
} else {
|
|
2060
|
-
|
|
2061
|
-
if (g) section = g;
|
|
2081
|
+
section = groupForSectionLabel(inner);
|
|
2062
2082
|
}
|
|
2063
2083
|
continue;
|
|
2064
2084
|
}
|
|
@@ -2134,9 +2154,9 @@ function enqueue(fn) {
|
|
|
2134
2154
|
);
|
|
2135
2155
|
return run;
|
|
2136
2156
|
}
|
|
2137
|
-
async function readJsonSafe(
|
|
2157
|
+
async function readJsonSafe(path5) {
|
|
2138
2158
|
try {
|
|
2139
|
-
return JSON.parse(await readTextFile(
|
|
2159
|
+
return JSON.parse(await readTextFile(path5));
|
|
2140
2160
|
} catch {
|
|
2141
2161
|
return null;
|
|
2142
2162
|
}
|
|
@@ -2148,9 +2168,9 @@ function emptyModel() {
|
|
|
2148
2168
|
return { themes: { default: "default", themes: { default: { colors: {} } } }, variables: [], raw: [] };
|
|
2149
2169
|
}
|
|
2150
2170
|
async function readModel(root) {
|
|
2151
|
-
const
|
|
2152
|
-
if (!await fileExists(
|
|
2153
|
-
return parseThemeCss(await readTextFile(
|
|
2171
|
+
const path5 = themeCssPath(root);
|
|
2172
|
+
if (!await fileExists(path5)) return emptyModel();
|
|
2173
|
+
return parseThemeCss(await readTextFile(path5), configService.getBreakpoints());
|
|
2154
2174
|
}
|
|
2155
2175
|
async function writeModel(root, model) {
|
|
2156
2176
|
const css = serializeThemeCss(model, scaleConfig());
|
|
@@ -2880,8 +2900,880 @@ async function handleItemRoute(collection, slug, cmsService) {
|
|
|
2880
2900
|
return jsonResponse({ item: match.item });
|
|
2881
2901
|
}
|
|
2882
2902
|
|
|
2883
|
-
// lib/server/routes/api/
|
|
2903
|
+
// lib/server/routes/api/cssAudit.ts
|
|
2904
|
+
import { existsSync as existsSync5, readdirSync as readdirSync2, readFileSync as readFileSync2, statSync } from "node:fs";
|
|
2884
2905
|
import path from "node:path";
|
|
2906
|
+
|
|
2907
|
+
// lib/server/cssAudit.ts
|
|
2908
|
+
import * as csstree from "css-tree";
|
|
2909
|
+
var DEFAULT_UTILITY_HINTS = [
|
|
2910
|
+
// Spacing / sizing
|
|
2911
|
+
"^(margin|padding|gap)(-|$)",
|
|
2912
|
+
"^no-(padding|margin|overflow)",
|
|
2913
|
+
"(^|-)auto-margin$",
|
|
2914
|
+
"^(max|min)-(w|h|width|height)",
|
|
2915
|
+
"^(w|h)-[0-9]",
|
|
2916
|
+
"^size-[0-9]",
|
|
2917
|
+
"^(small|medium|large)(-|$)",
|
|
2918
|
+
// Layout / display
|
|
2919
|
+
"^align(-|$)",
|
|
2920
|
+
"^justify(-|$)",
|
|
2921
|
+
"^(grid|col|flex)-",
|
|
2922
|
+
"^container(-|$)",
|
|
2923
|
+
"(^|-)(overflow|opacity|relative|absolute|sticky|inline|centered)(-|$)",
|
|
2924
|
+
"^(hide|show|hidden|visible)$",
|
|
2925
|
+
// State / Webflow combo classes
|
|
2926
|
+
"^is--?",
|
|
2927
|
+
"^has--?",
|
|
2928
|
+
"^on-h[0-9]",
|
|
2929
|
+
"^type-[0-9]+$",
|
|
2930
|
+
"(^|-)active$",
|
|
2931
|
+
// Colors
|
|
2932
|
+
"(^|-)(white|black|dark|light|primary|secondary|grey|gray)(-|$)",
|
|
2933
|
+
// Cross-cutting UI
|
|
2934
|
+
"(^|-)arrow",
|
|
2935
|
+
// Relume / newer-Webflow utility families (text-size-*, heading-style-*, spacer-*, max-width-*)
|
|
2936
|
+
"^text-(size|align|color|weight|style)",
|
|
2937
|
+
"^heading-style",
|
|
2938
|
+
"^(spacer|max-width|background-color)(-|$)"
|
|
2939
|
+
];
|
|
2940
|
+
var DEFAULT_WEBFLOW_AUTO = [
|
|
2941
|
+
"^(div-block|text-block|link-block|bold-text|block-quote|rich-text-block|html-embed)(-\\d+)?$",
|
|
2942
|
+
"^(heading|paragraph|image|list|list-item|figure|grid|columns?|field-label|text-field)-\\d+$"
|
|
2943
|
+
];
|
|
2944
|
+
var CLASS_TOKEN_RE = /\.(-?[_a-zA-Z][\w-]*)/g;
|
|
2945
|
+
var FUNCTIONAL_PSEUDO_RE = /:(not|is|where|has|matches)\([^()]*\)/gi;
|
|
2946
|
+
function emptyBucket() {
|
|
2947
|
+
return { classes: 0, rules: 0, declarations: 0, approxBytes: 0 };
|
|
2948
|
+
}
|
|
2949
|
+
function classTokens(selector) {
|
|
2950
|
+
const out = [];
|
|
2951
|
+
let m;
|
|
2952
|
+
CLASS_TOKEN_RE.lastIndex = 0;
|
|
2953
|
+
while ((m = CLASS_TOKEN_RE.exec(selector)) !== null) {
|
|
2954
|
+
if (m[1]) out.push(m[1]);
|
|
2955
|
+
}
|
|
2956
|
+
return out;
|
|
2957
|
+
}
|
|
2958
|
+
function sectionPrefix(name) {
|
|
2959
|
+
const seg = name.split(/[-_]/)[0] ?? name;
|
|
2960
|
+
const stripped = seg.replace(/\d+$/, "");
|
|
2961
|
+
return stripped.length > 0 ? stripped : seg;
|
|
2962
|
+
}
|
|
2963
|
+
function commonPrefixLength(a, b) {
|
|
2964
|
+
const max = Math.min(a.length, b.length);
|
|
2965
|
+
let i = 0;
|
|
2966
|
+
while (i < max && a[i] === b[i]) i++;
|
|
2967
|
+
return i;
|
|
2968
|
+
}
|
|
2969
|
+
function editDistance(a, b) {
|
|
2970
|
+
const m = a.length;
|
|
2971
|
+
const n = b.length;
|
|
2972
|
+
if (m === 0) return n;
|
|
2973
|
+
if (n === 0) return m;
|
|
2974
|
+
let prev = Array.from({ length: n + 1 }, (_, i) => i);
|
|
2975
|
+
let curr = new Array(n + 1).fill(0);
|
|
2976
|
+
for (let i = 1; i <= m; i++) {
|
|
2977
|
+
curr[0] = i;
|
|
2978
|
+
for (let j = 1; j <= n; j++) {
|
|
2979
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
2980
|
+
curr[j] = Math.min((prev[j] ?? 0) + 1, (curr[j - 1] ?? 0) + 1, (prev[j - 1] ?? 0) + cost);
|
|
2981
|
+
}
|
|
2982
|
+
[prev, curr] = [curr, prev];
|
|
2983
|
+
}
|
|
2984
|
+
return prev[n] ?? 0;
|
|
2985
|
+
}
|
|
2986
|
+
function nearestKeptPrefix(prefix, kept) {
|
|
2987
|
+
let best = null;
|
|
2988
|
+
let bestShared = -1;
|
|
2989
|
+
for (const candidate of kept) {
|
|
2990
|
+
if (Math.abs(candidate.length - prefix.length) > 1) continue;
|
|
2991
|
+
const shared = commonPrefixLength(prefix, candidate);
|
|
2992
|
+
if (shared < 2) continue;
|
|
2993
|
+
if (editDistance(prefix, candidate) !== 1) continue;
|
|
2994
|
+
if (shared > bestShared) {
|
|
2995
|
+
bestShared = shared;
|
|
2996
|
+
best = candidate;
|
|
2997
|
+
}
|
|
2998
|
+
}
|
|
2999
|
+
return best;
|
|
3000
|
+
}
|
|
3001
|
+
function analyzeSelector(selector) {
|
|
3002
|
+
const cleaned = selector.replace(FUNCTIONAL_PSEUDO_RE, " ");
|
|
3003
|
+
const segments = cleaned.split(/\s*[>+~]\s*|\s+/).filter((s) => s.trim().length > 0);
|
|
3004
|
+
const subject = segments[segments.length - 1] ?? cleaned;
|
|
3005
|
+
const subjectClasses = classTokens(subject);
|
|
3006
|
+
const allClasses = classTokens(selector);
|
|
3007
|
+
const compound = segments.length > 1 || subjectClasses.length > 1;
|
|
3008
|
+
return { allClasses, subjectClasses, compound };
|
|
3009
|
+
}
|
|
3010
|
+
function countDeclarations(block) {
|
|
3011
|
+
let count = 0;
|
|
3012
|
+
if (block && block.type === "Block") {
|
|
3013
|
+
block.children.forEach((child) => {
|
|
3014
|
+
if (child.type === "Declaration") count++;
|
|
3015
|
+
});
|
|
3016
|
+
}
|
|
3017
|
+
return count;
|
|
3018
|
+
}
|
|
3019
|
+
function extractRules(css) {
|
|
3020
|
+
const ast = csstree.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3021
|
+
const out = [];
|
|
3022
|
+
const visit = (container, media, insideSkippedAtrule) => {
|
|
3023
|
+
const children = "children" in container ? container.children : null;
|
|
3024
|
+
if (!children) return;
|
|
3025
|
+
children.forEach((node) => {
|
|
3026
|
+
if (node.type === "Atrule") {
|
|
3027
|
+
const name = node.name.toLowerCase();
|
|
3028
|
+
if (name === "media") {
|
|
3029
|
+
const prelude = node.prelude ? csstree.generate(node.prelude) : "";
|
|
3030
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3031
|
+
} else if (name === "supports") {
|
|
3032
|
+
if (node.block) visit(node.block, media, false);
|
|
3033
|
+
} else if (node.block) {
|
|
3034
|
+
visit(node.block, media, true);
|
|
3035
|
+
}
|
|
3036
|
+
return;
|
|
3037
|
+
}
|
|
3038
|
+
if (node.type !== "Rule" || insideSkippedAtrule) return;
|
|
3039
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3040
|
+
const decls = countDeclarations(node.block);
|
|
3041
|
+
const ruleBytes = csstree.generate(node).length;
|
|
3042
|
+
const classSelectors = [];
|
|
3043
|
+
node.prelude.children.forEach((selectorNode) => {
|
|
3044
|
+
const text = csstree.generate(selectorNode);
|
|
3045
|
+
const shape = analyzeSelector(text);
|
|
3046
|
+
if (shape.allClasses.length > 0) classSelectors.push({ text, shape });
|
|
3047
|
+
});
|
|
3048
|
+
if (classSelectors.length === 0) return;
|
|
3049
|
+
const perBytes = Math.max(1, Math.round(ruleBytes / classSelectors.length));
|
|
3050
|
+
for (const { text, shape } of classSelectors) {
|
|
3051
|
+
out.push({
|
|
3052
|
+
selector: text,
|
|
3053
|
+
media,
|
|
3054
|
+
classes: shape.allClasses,
|
|
3055
|
+
subjectClasses: shape.subjectClasses,
|
|
3056
|
+
declarations: decls,
|
|
3057
|
+
bytes: perBytes,
|
|
3058
|
+
compound: shape.compound
|
|
3059
|
+
});
|
|
3060
|
+
}
|
|
3061
|
+
});
|
|
3062
|
+
};
|
|
3063
|
+
visit(ast, null, false);
|
|
3064
|
+
return out;
|
|
3065
|
+
}
|
|
3066
|
+
function auditStylesheet(input) {
|
|
3067
|
+
const { css, pages, options = {} } = input;
|
|
3068
|
+
const selectorSampleLimit = options.selectorSampleLimit ?? 5;
|
|
3069
|
+
const flagLimit = options.flagLimit ?? 200;
|
|
3070
|
+
const minSectionClasses = options.minSectionClasses ?? 3;
|
|
3071
|
+
const miscBucketName = options.miscBucketName ?? "misc";
|
|
3072
|
+
const mergeTypos = options.mergeTypos ?? true;
|
|
3073
|
+
const utilityHints = (options.utilityHints ?? DEFAULT_UTILITY_HINTS).map((s) => new RegExp(s));
|
|
3074
|
+
const webflowAutoPatterns = (options.webflowAuto ?? DEFAULT_WEBFLOW_AUTO).map((s) => new RegExp(s));
|
|
3075
|
+
const pageCount = pages.length;
|
|
3076
|
+
const sharedMinPages = options.sharedMinPages ?? Math.max(2, Math.ceil(pageCount / 2));
|
|
3077
|
+
const pageUseCount = /* @__PURE__ */ new Map();
|
|
3078
|
+
for (const page of pages) {
|
|
3079
|
+
for (const cls of new Set(page.classes)) {
|
|
3080
|
+
pageUseCount.set(cls, (pageUseCount.get(cls) ?? 0) + 1);
|
|
3081
|
+
}
|
|
3082
|
+
}
|
|
3083
|
+
const ast = csstree.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3084
|
+
const classMap = /* @__PURE__ */ new Map();
|
|
3085
|
+
const rules = [];
|
|
3086
|
+
const breakpoints = [];
|
|
3087
|
+
const seenMedia = /* @__PURE__ */ new Set();
|
|
3088
|
+
const base = emptyBucket();
|
|
3089
|
+
let totalRules = 0;
|
|
3090
|
+
let totalSelectors = 0;
|
|
3091
|
+
let totalDeclarations = 0;
|
|
3092
|
+
const accumFor = (name) => {
|
|
3093
|
+
let a = classMap.get(name);
|
|
3094
|
+
if (!a) {
|
|
3095
|
+
a = { name, ruleCount: 0, declarations: 0, breakpoints: /* @__PURE__ */ new Set(), selectors: [], ownsSimple: false };
|
|
3096
|
+
classMap.set(name, a);
|
|
3097
|
+
}
|
|
3098
|
+
return a;
|
|
3099
|
+
};
|
|
3100
|
+
const visit = (container, media, insideSkippedAtrule) => {
|
|
3101
|
+
const children = "children" in container ? container.children : null;
|
|
3102
|
+
if (!children) return;
|
|
3103
|
+
children.forEach((node) => {
|
|
3104
|
+
if (node.type === "Atrule") {
|
|
3105
|
+
const name = node.name.toLowerCase();
|
|
3106
|
+
if (name === "media") {
|
|
3107
|
+
const prelude = node.prelude ? csstree.generate(node.prelude) : "";
|
|
3108
|
+
if (!seenMedia.has(prelude)) {
|
|
3109
|
+
seenMedia.add(prelude);
|
|
3110
|
+
breakpoints.push(prelude);
|
|
3111
|
+
}
|
|
3112
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3113
|
+
} else if (name === "supports") {
|
|
3114
|
+
if (node.block) visit(node.block, media, false);
|
|
3115
|
+
} else if (node.block) {
|
|
3116
|
+
visit(node.block, media, true);
|
|
3117
|
+
}
|
|
3118
|
+
return;
|
|
3119
|
+
}
|
|
3120
|
+
if (node.type !== "Rule" || insideSkippedAtrule) return;
|
|
3121
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3122
|
+
const ruleBytes = csstree.generate(node).length;
|
|
3123
|
+
const decls = countDeclarations(node.block);
|
|
3124
|
+
totalDeclarations += decls;
|
|
3125
|
+
totalRules += 1;
|
|
3126
|
+
const ruleAllClasses = /* @__PURE__ */ new Set();
|
|
3127
|
+
const ruleSubjectClasses = /* @__PURE__ */ new Set();
|
|
3128
|
+
let hasAnyClass = false;
|
|
3129
|
+
node.prelude.children.forEach((selectorNode) => {
|
|
3130
|
+
totalSelectors += 1;
|
|
3131
|
+
const selectorText = csstree.generate(selectorNode);
|
|
3132
|
+
const shape = analyzeSelector(selectorText);
|
|
3133
|
+
if (shape.allClasses.length === 0) return;
|
|
3134
|
+
hasAnyClass = true;
|
|
3135
|
+
for (const c of shape.allClasses) ruleAllClasses.add(c);
|
|
3136
|
+
for (const c of shape.subjectClasses) ruleSubjectClasses.add(c);
|
|
3137
|
+
const subjectSet = new Set(shape.subjectClasses);
|
|
3138
|
+
for (const cls of shape.allClasses) {
|
|
3139
|
+
const a = accumFor(cls);
|
|
3140
|
+
a.ruleCount += 1;
|
|
3141
|
+
a.declarations += decls;
|
|
3142
|
+
a.breakpoints.add(media ?? "");
|
|
3143
|
+
if (a.selectors.length < selectorSampleLimit && !a.selectors.includes(selectorText)) {
|
|
3144
|
+
a.selectors.push(selectorText);
|
|
3145
|
+
}
|
|
3146
|
+
if (subjectSet.size === 1 && subjectSet.has(cls)) a.ownsSimple = true;
|
|
3147
|
+
}
|
|
3148
|
+
});
|
|
3149
|
+
if (!hasAnyClass) {
|
|
3150
|
+
base.rules += 1;
|
|
3151
|
+
base.declarations += decls;
|
|
3152
|
+
base.approxBytes += ruleBytes;
|
|
3153
|
+
return;
|
|
3154
|
+
}
|
|
3155
|
+
rules.push({
|
|
3156
|
+
media,
|
|
3157
|
+
bytes: ruleBytes,
|
|
3158
|
+
declarations: decls,
|
|
3159
|
+
classes: [...ruleAllClasses],
|
|
3160
|
+
subjectClasses: [...ruleSubjectClasses]
|
|
3161
|
+
});
|
|
3162
|
+
});
|
|
3163
|
+
};
|
|
3164
|
+
visit(ast, null, false);
|
|
3165
|
+
const prelim = /* @__PURE__ */ new Map();
|
|
3166
|
+
const sectionSizes = /* @__PURE__ */ new Map();
|
|
3167
|
+
for (const a of classMap.values()) {
|
|
3168
|
+
const pagesUsedIn = pageUseCount.get(a.name) ?? 0;
|
|
3169
|
+
let category;
|
|
3170
|
+
let bucket;
|
|
3171
|
+
if (/^w-/.test(a.name)) {
|
|
3172
|
+
category = "vendor";
|
|
3173
|
+
bucket = "vendor";
|
|
3174
|
+
} else if (webflowAutoPatterns.some((re) => re.test(a.name))) {
|
|
3175
|
+
category = "webflow-auto";
|
|
3176
|
+
bucket = "webflow-auto";
|
|
3177
|
+
} else if (utilityHints.some((re) => re.test(a.name)) || pagesUsedIn >= sharedMinPages) {
|
|
3178
|
+
category = "shared";
|
|
3179
|
+
bucket = "shared";
|
|
3180
|
+
} else {
|
|
3181
|
+
category = "section";
|
|
3182
|
+
bucket = sectionPrefix(a.name);
|
|
3183
|
+
sectionSizes.set(bucket, (sectionSizes.get(bucket) ?? 0) + 1);
|
|
3184
|
+
}
|
|
3185
|
+
prelim.set(a.name, { category, bucket, pagesUsedIn, accum: a });
|
|
3186
|
+
}
|
|
3187
|
+
const keptSections = /* @__PURE__ */ new Set();
|
|
3188
|
+
for (const [prefix, count] of sectionSizes) {
|
|
3189
|
+
if (count >= minSectionClasses) keptSections.add(prefix);
|
|
3190
|
+
}
|
|
3191
|
+
const sectionRemap = /* @__PURE__ */ new Map();
|
|
3192
|
+
for (const [prefix, count] of sectionSizes) {
|
|
3193
|
+
if (keptSections.has(prefix) || count === 0) continue;
|
|
3194
|
+
const near = mergeTypos && prefix.length >= 4 ? nearestKeptPrefix(prefix, keptSections) : null;
|
|
3195
|
+
sectionRemap.set(prefix, near ? { bucket: near, reason: "typo" } : { bucket: miscBucketName, reason: "small" });
|
|
3196
|
+
}
|
|
3197
|
+
const foldedAgg = /* @__PURE__ */ new Map();
|
|
3198
|
+
const classified = /* @__PURE__ */ new Map();
|
|
3199
|
+
const classes = [];
|
|
3200
|
+
for (const [name, p] of prelim) {
|
|
3201
|
+
let bucket = p.bucket;
|
|
3202
|
+
if (p.category === "section") {
|
|
3203
|
+
const remap = sectionRemap.get(p.bucket);
|
|
3204
|
+
if (remap) {
|
|
3205
|
+
bucket = remap.bucket;
|
|
3206
|
+
const key = `${p.bucket}\u2192${remap.bucket}`;
|
|
3207
|
+
const agg = foldedAgg.get(key) ?? { from: p.bucket, into: remap.bucket, reason: remap.reason, classes: 0 };
|
|
3208
|
+
agg.classes += 1;
|
|
3209
|
+
foldedAgg.set(key, agg);
|
|
3210
|
+
}
|
|
3211
|
+
}
|
|
3212
|
+
classified.set(name, { category: p.category, bucket });
|
|
3213
|
+
classes.push({
|
|
3214
|
+
name,
|
|
3215
|
+
category: p.category,
|
|
3216
|
+
proposedBucket: bucket,
|
|
3217
|
+
ruleCount: p.accum.ruleCount,
|
|
3218
|
+
declarations: p.accum.declarations,
|
|
3219
|
+
breakpoints: [...p.accum.breakpoints].sort(),
|
|
3220
|
+
pagesUsedIn: p.pagesUsedIn,
|
|
3221
|
+
selectors: p.accum.selectors,
|
|
3222
|
+
compoundOnly: !p.accum.ownsSimple
|
|
3223
|
+
});
|
|
3224
|
+
}
|
|
3225
|
+
classes.sort((x, y) => y.declarations - x.declarations || x.name.localeCompare(y.name));
|
|
3226
|
+
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));
|
|
3227
|
+
const vendor = emptyBucket();
|
|
3228
|
+
const webflowAuto = emptyBucket();
|
|
3229
|
+
const shared = emptyBucket();
|
|
3230
|
+
const sections = {};
|
|
3231
|
+
const crossBoundary = [];
|
|
3232
|
+
const bucketStat = (bucket) => {
|
|
3233
|
+
if (bucket === "vendor") return vendor;
|
|
3234
|
+
if (bucket === "webflow-auto") return webflowAuto;
|
|
3235
|
+
if (bucket === "shared") return shared;
|
|
3236
|
+
let stat = sections[bucket];
|
|
3237
|
+
if (!stat) {
|
|
3238
|
+
stat = emptyBucket();
|
|
3239
|
+
sections[bucket] = stat;
|
|
3240
|
+
}
|
|
3241
|
+
return stat;
|
|
3242
|
+
};
|
|
3243
|
+
for (const rule of rules) {
|
|
3244
|
+
const ownerPool = rule.subjectClasses.length > 0 ? rule.subjectClasses : rule.classes;
|
|
3245
|
+
let owner = "base";
|
|
3246
|
+
let bestRank = -1;
|
|
3247
|
+
const sectionBucketsTouched = /* @__PURE__ */ new Set();
|
|
3248
|
+
for (const cls of rule.classes) {
|
|
3249
|
+
const c = classified.get(cls);
|
|
3250
|
+
if (c?.category === "section" && c.bucket !== miscBucketName) sectionBucketsTouched.add(c.bucket);
|
|
3251
|
+
}
|
|
3252
|
+
for (const cls of ownerPool) {
|
|
3253
|
+
const c = classified.get(cls);
|
|
3254
|
+
if (!c) continue;
|
|
3255
|
+
const rank = c.category === "section" ? 4 : c.category === "shared" ? 3 : c.category === "webflow-auto" ? 2 : 1;
|
|
3256
|
+
if (rank > bestRank) {
|
|
3257
|
+
bestRank = rank;
|
|
3258
|
+
owner = c.bucket;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
const stat = bucketStat(owner);
|
|
3262
|
+
stat.rules += 1;
|
|
3263
|
+
stat.declarations += rule.declarations;
|
|
3264
|
+
stat.approxBytes += rule.bytes;
|
|
3265
|
+
if (sectionBucketsTouched.size >= 2 && crossBoundary.length < flagLimit) {
|
|
3266
|
+
crossBoundary.push({
|
|
3267
|
+
selector: rule.classes.map((c) => `.${c}`).join(""),
|
|
3268
|
+
media: rule.media,
|
|
3269
|
+
classes: rule.classes,
|
|
3270
|
+
buckets: [...sectionBucketsTouched].sort()
|
|
3271
|
+
});
|
|
3272
|
+
}
|
|
3273
|
+
}
|
|
3274
|
+
for (const info of classes) {
|
|
3275
|
+
bucketStat(info.proposedBucket).classes += 1;
|
|
3276
|
+
}
|
|
3277
|
+
const fuzzyMiddle = [];
|
|
3278
|
+
const deadClasses = [];
|
|
3279
|
+
for (const info of classes) {
|
|
3280
|
+
if (info.category === "section" && info.pagesUsedIn >= 2 && info.pagesUsedIn < sharedMinPages) {
|
|
3281
|
+
fuzzyMiddle.push(info.name);
|
|
3282
|
+
}
|
|
3283
|
+
if (info.category !== "vendor" && info.pagesUsedIn === 0) {
|
|
3284
|
+
deadClasses.push(info.name);
|
|
3285
|
+
}
|
|
3286
|
+
}
|
|
3287
|
+
const pageOnlyClasses = [];
|
|
3288
|
+
for (const cls of pageUseCount.keys()) {
|
|
3289
|
+
if (!classMap.has(cls)) pageOnlyClasses.push(cls);
|
|
3290
|
+
}
|
|
3291
|
+
pageOnlyClasses.sort();
|
|
3292
|
+
deadClasses.sort();
|
|
3293
|
+
return {
|
|
3294
|
+
source: {
|
|
3295
|
+
bytes: css.length,
|
|
3296
|
+
rules: totalRules,
|
|
3297
|
+
selectors: totalSelectors,
|
|
3298
|
+
declarations: totalDeclarations,
|
|
3299
|
+
pageCount,
|
|
3300
|
+
sharedMinPages
|
|
3301
|
+
},
|
|
3302
|
+
breakpoints,
|
|
3303
|
+
buckets: { base, vendor, webflowAuto, shared, sections },
|
|
3304
|
+
classes,
|
|
3305
|
+
flags: {
|
|
3306
|
+
crossBoundarySelectors: crossBoundary,
|
|
3307
|
+
fuzzyMiddle: fuzzyMiddle.slice(0, flagLimit),
|
|
3308
|
+
deadClasses: deadClasses.slice(0, flagLimit),
|
|
3309
|
+
pageOnlyClasses: pageOnlyClasses.slice(0, flagLimit),
|
|
3310
|
+
foldedSections
|
|
3311
|
+
}
|
|
3312
|
+
};
|
|
3313
|
+
}
|
|
3314
|
+
function collectClassesFromPageData(pageData) {
|
|
3315
|
+
const out = /* @__PURE__ */ new Set();
|
|
3316
|
+
const walk = (value) => {
|
|
3317
|
+
if (!value) return;
|
|
3318
|
+
if (Array.isArray(value)) {
|
|
3319
|
+
for (const item of value) walk(item);
|
|
3320
|
+
return;
|
|
3321
|
+
}
|
|
3322
|
+
if (typeof value === "object") {
|
|
3323
|
+
for (const [key, v] of Object.entries(value)) {
|
|
3324
|
+
if ((key === "class" || key === "className") && typeof v === "string") {
|
|
3325
|
+
for (const token of v.split(/\s+/)) {
|
|
3326
|
+
if (token) out.add(token);
|
|
3327
|
+
}
|
|
3328
|
+
} else {
|
|
3329
|
+
walk(v);
|
|
3330
|
+
}
|
|
3331
|
+
}
|
|
3332
|
+
}
|
|
3333
|
+
};
|
|
3334
|
+
walk(pageData);
|
|
3335
|
+
return [...out];
|
|
3336
|
+
}
|
|
3337
|
+
function renderAuditMarkdown(report) {
|
|
3338
|
+
const { source, buckets, breakpoints, flags } = report;
|
|
3339
|
+
const kb = (n) => `${(n / 1024).toFixed(1)} KB`;
|
|
3340
|
+
const lines = [];
|
|
3341
|
+
lines.push("# CSS audit");
|
|
3342
|
+
lines.push("");
|
|
3343
|
+
lines.push(
|
|
3344
|
+
`**${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`
|
|
3345
|
+
);
|
|
3346
|
+
lines.push("");
|
|
3347
|
+
lines.push(`Breakpoints: ${breakpoints.length ? breakpoints.map((b) => `\`${b}\``).join(", ") : "(none)"}`);
|
|
3348
|
+
lines.push("");
|
|
3349
|
+
lines.push("## Proposed buckets");
|
|
3350
|
+
lines.push("");
|
|
3351
|
+
lines.push("| bucket | classes | rules | declarations | ~size |");
|
|
3352
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
3353
|
+
const row = (name, b) => `| ${name} | ${b.classes} | ${b.rules} | ${b.declarations} | ${kb(b.approxBytes)} |`;
|
|
3354
|
+
lines.push(row("base (reset/typography)", buckets.base));
|
|
3355
|
+
lines.push(row("vendor (.w-*)", buckets.vendor));
|
|
3356
|
+
lines.push(row("webflow-auto (div-block/text-block/\u2026)", buckets.webflowAuto));
|
|
3357
|
+
lines.push(row("shared", buckets.shared));
|
|
3358
|
+
const sectionEntries = Object.entries(buckets.sections).sort((a, b) => b[1].approxBytes - a[1].approxBytes);
|
|
3359
|
+
for (const [prefix, stat] of sectionEntries) {
|
|
3360
|
+
if (prefix === "misc") continue;
|
|
3361
|
+
lines.push(row(`section: ${prefix}`, stat));
|
|
3362
|
+
}
|
|
3363
|
+
const misc = buckets.sections.misc;
|
|
3364
|
+
if (misc) lines.push(row("misc (folded small/orphan)", misc));
|
|
3365
|
+
lines.push("");
|
|
3366
|
+
lines.push(`_${sectionEntries.filter(([p]) => p !== "misc").length} real sections + ${misc ? "misc" : "no misc"}_`);
|
|
3367
|
+
lines.push("");
|
|
3368
|
+
lines.push("## Flags");
|
|
3369
|
+
lines.push("");
|
|
3370
|
+
lines.push(`- Cross-boundary selectors (touch \u22652 sections): **${flags.crossBoundarySelectors.length}**`);
|
|
3371
|
+
lines.push(`- Fuzzy-middle classes (near shared threshold): **${flags.fuzzyMiddle.length}**`);
|
|
3372
|
+
lines.push(`- Dead classes (defined, unused on any page): **${flags.deadClasses.length}**`);
|
|
3373
|
+
lines.push(`- Page-only classes (used in pages, not in this sheet): **${flags.pageOnlyClasses.length}**`);
|
|
3374
|
+
const foldedClasses = flags.foldedSections.reduce((n, f) => n + f.classes, 0);
|
|
3375
|
+
lines.push(
|
|
3376
|
+
`- Folded section buckets (small/typo \u2192 misc/kept): **${flags.foldedSections.length}** (${foldedClasses} classes)`
|
|
3377
|
+
);
|
|
3378
|
+
const typos = flags.foldedSections.filter((f) => f.reason === "typo");
|
|
3379
|
+
if (typos.length) {
|
|
3380
|
+
lines.push("");
|
|
3381
|
+
lines.push("Typo merges:");
|
|
3382
|
+
for (const f of typos) lines.push(`- \`${f.from}\` \u2192 \`${f.into}\` (${f.classes})`);
|
|
3383
|
+
}
|
|
3384
|
+
if (flags.crossBoundarySelectors.length) {
|
|
3385
|
+
lines.push("");
|
|
3386
|
+
lines.push("Sample cross-boundary selectors:");
|
|
3387
|
+
for (const f of flags.crossBoundarySelectors.slice(0, 10)) {
|
|
3388
|
+
lines.push(`- \`${f.selector}\`${f.media ? ` @ ${f.media}` : ""} \u2192 ${f.buckets.join(" + ")}`);
|
|
3389
|
+
}
|
|
3390
|
+
}
|
|
3391
|
+
return lines.join("\n");
|
|
3392
|
+
}
|
|
3393
|
+
|
|
3394
|
+
// lib/server/routes/api/cssAudit.ts
|
|
3395
|
+
var SKIP_DIRS = /* @__PURE__ */ new Set(["node_modules", ".git", "dist", ".meno", "rendered-websites"]);
|
|
3396
|
+
function findMirrorCss(root, maxDepth = 6) {
|
|
3397
|
+
const found = [];
|
|
3398
|
+
const walk = (dir, depth) => {
|
|
3399
|
+
if (depth > maxDepth) return;
|
|
3400
|
+
let entries;
|
|
3401
|
+
try {
|
|
3402
|
+
entries = readdirSync2(dir);
|
|
3403
|
+
} catch {
|
|
3404
|
+
return;
|
|
3405
|
+
}
|
|
3406
|
+
for (const entry of entries) {
|
|
3407
|
+
const full = path.join(dir, entry);
|
|
3408
|
+
let isDir = false;
|
|
3409
|
+
try {
|
|
3410
|
+
isDir = statSync(full).isDirectory();
|
|
3411
|
+
} catch {
|
|
3412
|
+
continue;
|
|
3413
|
+
}
|
|
3414
|
+
if (isDir) {
|
|
3415
|
+
if (SKIP_DIRS.has(entry)) continue;
|
|
3416
|
+
walk(full, depth + 1);
|
|
3417
|
+
} else if (entry.endsWith(".css") && full.replace(/\\/g, "/").includes("/_mirror/css/")) {
|
|
3418
|
+
found.push(full);
|
|
3419
|
+
}
|
|
3420
|
+
}
|
|
3421
|
+
};
|
|
3422
|
+
walk(root, 0);
|
|
3423
|
+
return found.sort();
|
|
3424
|
+
}
|
|
3425
|
+
function resolveCssParam(root, rel) {
|
|
3426
|
+
const resolved = path.resolve(root, rel);
|
|
3427
|
+
const normalizedRoot = path.resolve(root);
|
|
3428
|
+
if (resolved !== normalizedRoot && !resolved.startsWith(normalizedRoot + path.sep)) return null;
|
|
3429
|
+
return existsSync5(resolved) ? resolved : null;
|
|
3430
|
+
}
|
|
3431
|
+
function handleCssAuditRoute(url, pageService) {
|
|
3432
|
+
const root = getProjectRoot();
|
|
3433
|
+
const cssParam = url.searchParams.get("css");
|
|
3434
|
+
let cssFiles;
|
|
3435
|
+
if (cssParam) {
|
|
3436
|
+
const resolved = resolveCssParam(root, cssParam);
|
|
3437
|
+
if (!resolved) {
|
|
3438
|
+
return jsonResponse({ error: `Stylesheet not found or outside project: ${cssParam}` }, { status: 404 });
|
|
3439
|
+
}
|
|
3440
|
+
cssFiles = [resolved];
|
|
3441
|
+
} else {
|
|
3442
|
+
cssFiles = findMirrorCss(root);
|
|
3443
|
+
if (cssFiles.length === 0) {
|
|
3444
|
+
return jsonResponse(
|
|
3445
|
+
{
|
|
3446
|
+
error: "No mirror stylesheet found",
|
|
3447
|
+
hint: "Expected a Webflow export at public/_mirror/css/*.css. Pass ?css=<relative path> to audit a specific stylesheet."
|
|
3448
|
+
},
|
|
3449
|
+
{ status: 404 }
|
|
3450
|
+
);
|
|
3451
|
+
}
|
|
3452
|
+
}
|
|
3453
|
+
const css = cssFiles.map((f) => readFileSync2(f, "utf8")).join("\n");
|
|
3454
|
+
const pages = [];
|
|
3455
|
+
for (const pagePath of pageService.getAllPagePaths()) {
|
|
3456
|
+
const data = pageService.getPageData(pagePath);
|
|
3457
|
+
if (!data) continue;
|
|
3458
|
+
pages.push({ path: pagePath, classes: collectClassesFromPageData(data) });
|
|
3459
|
+
}
|
|
3460
|
+
const intParam = (name) => {
|
|
3461
|
+
const raw = url.searchParams.get(name);
|
|
3462
|
+
if (!raw) return void 0;
|
|
3463
|
+
const n = Number.parseInt(raw, 10);
|
|
3464
|
+
return Number.isFinite(n) ? n : void 0;
|
|
3465
|
+
};
|
|
3466
|
+
const options = {};
|
|
3467
|
+
const sharedMinPages = intParam("sharedMinPages");
|
|
3468
|
+
const minSectionClasses = intParam("minSectionClasses");
|
|
3469
|
+
if (sharedMinPages !== void 0) options.sharedMinPages = sharedMinPages;
|
|
3470
|
+
if (minSectionClasses !== void 0) options.minSectionClasses = minSectionClasses;
|
|
3471
|
+
const report = auditStylesheet({
|
|
3472
|
+
css,
|
|
3473
|
+
pages,
|
|
3474
|
+
options: Object.keys(options).length > 0 ? options : void 0
|
|
3475
|
+
});
|
|
3476
|
+
const cssFilesRel = cssFiles.map((f) => path.relative(root, f));
|
|
3477
|
+
const format = url.searchParams.get("format");
|
|
3478
|
+
if (format === "md" || format === "markdown") {
|
|
3479
|
+
const md = `${renderAuditMarkdown(report)}
|
|
3480
|
+
|
|
3481
|
+
_Audited: ${cssFilesRel.join(", ")}_
|
|
3482
|
+
`;
|
|
3483
|
+
return new Response(md, {
|
|
3484
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8", "Cache-Control": "no-store, max-age=0" }
|
|
3485
|
+
});
|
|
3486
|
+
}
|
|
3487
|
+
return jsonResponse({ ...report, cssFiles: cssFilesRel });
|
|
3488
|
+
}
|
|
3489
|
+
|
|
3490
|
+
// lib/server/routes/api/deMirror.ts
|
|
3491
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
3492
|
+
import path2 from "node:path";
|
|
3493
|
+
|
|
3494
|
+
// lib/server/deMirror.ts
|
|
3495
|
+
import * as csstree2 from "css-tree";
|
|
3496
|
+
var CLASS_TOKEN_RE2 = /\.(-?[_a-zA-Z][\w-]*)/g;
|
|
3497
|
+
var FUNCTIONAL_PSEUDO_RE2 = /:(not|is|where|has|matches)\([^()]*\)/gi;
|
|
3498
|
+
function classTokens2(selector) {
|
|
3499
|
+
const out = [];
|
|
3500
|
+
let m;
|
|
3501
|
+
CLASS_TOKEN_RE2.lastIndex = 0;
|
|
3502
|
+
while ((m = CLASS_TOKEN_RE2.exec(selector)) !== null) {
|
|
3503
|
+
if (m[1]) out.push(m[1]);
|
|
3504
|
+
}
|
|
3505
|
+
return out;
|
|
3506
|
+
}
|
|
3507
|
+
function subjectClassesOf(selector) {
|
|
3508
|
+
const cleaned = selector.replace(FUNCTIONAL_PSEUDO_RE2, " ");
|
|
3509
|
+
const segments = cleaned.split(/\s*[>+~]\s*|\s+/).filter((s) => s.trim().length > 0);
|
|
3510
|
+
const subject = segments[segments.length - 1] ?? cleaned;
|
|
3511
|
+
return classTokens2(subject);
|
|
3512
|
+
}
|
|
3513
|
+
function declBlock(block) {
|
|
3514
|
+
const lines = [];
|
|
3515
|
+
if (block && block.type === "Block") {
|
|
3516
|
+
block.children.forEach((child) => {
|
|
3517
|
+
if (child.type !== "Declaration") return;
|
|
3518
|
+
const g = csstree2.generate(child);
|
|
3519
|
+
const i = g.indexOf(":");
|
|
3520
|
+
lines.push(` ${i > 0 ? `${g.slice(0, i)}: ${g.slice(i + 1)}` : g};`);
|
|
3521
|
+
});
|
|
3522
|
+
}
|
|
3523
|
+
return { text: lines.join("\n"), count: lines.length };
|
|
3524
|
+
}
|
|
3525
|
+
function captureClassRules(css) {
|
|
3526
|
+
const ast = csstree2.parse(css, { parseValue: false, parseCustomProperty: false });
|
|
3527
|
+
const out = [];
|
|
3528
|
+
const visit = (container, media, skip) => {
|
|
3529
|
+
const children = "children" in container ? container.children : null;
|
|
3530
|
+
if (!children) return;
|
|
3531
|
+
children.forEach((node) => {
|
|
3532
|
+
if (node.type === "Atrule") {
|
|
3533
|
+
const name = node.name.toLowerCase();
|
|
3534
|
+
if (name === "media") {
|
|
3535
|
+
const prelude = node.prelude ? csstree2.generate(node.prelude) : "";
|
|
3536
|
+
if (node.block) visit(node.block, prelude, false);
|
|
3537
|
+
} else if (name === "supports") {
|
|
3538
|
+
if (node.block) visit(node.block, media, false);
|
|
3539
|
+
} else if (node.block) {
|
|
3540
|
+
visit(node.block, media, true);
|
|
3541
|
+
}
|
|
3542
|
+
return;
|
|
3543
|
+
}
|
|
3544
|
+
if (node.type !== "Rule" || skip) return;
|
|
3545
|
+
if (node.prelude?.type !== "SelectorList") return;
|
|
3546
|
+
const selectors = [];
|
|
3547
|
+
const subject = /* @__PURE__ */ new Set();
|
|
3548
|
+
const all = /* @__PURE__ */ new Set();
|
|
3549
|
+
node.prelude.children.forEach((sel) => {
|
|
3550
|
+
const text = csstree2.generate(sel);
|
|
3551
|
+
const cls = classTokens2(text);
|
|
3552
|
+
if (cls.length === 0) return;
|
|
3553
|
+
selectors.push(text);
|
|
3554
|
+
for (const c of cls) all.add(c);
|
|
3555
|
+
for (const c of subjectClassesOf(text)) subject.add(c);
|
|
3556
|
+
});
|
|
3557
|
+
if (selectors.length === 0) return;
|
|
3558
|
+
const { text: decls, count } = declBlock(node.block);
|
|
3559
|
+
out.push({
|
|
3560
|
+
selector: selectors.join(", "),
|
|
3561
|
+
media,
|
|
3562
|
+
subjectClasses: [...subject],
|
|
3563
|
+
allClasses: [...all],
|
|
3564
|
+
declarations: count,
|
|
3565
|
+
bytes: csstree2.generate(node).length,
|
|
3566
|
+
text: `${selectors.join(",\n")} {
|
|
3567
|
+
${decls}
|
|
3568
|
+
}`
|
|
3569
|
+
});
|
|
3570
|
+
});
|
|
3571
|
+
};
|
|
3572
|
+
visit(ast, null, false);
|
|
3573
|
+
return out;
|
|
3574
|
+
}
|
|
3575
|
+
function assembleSlice(rules) {
|
|
3576
|
+
const base = rules.filter((r) => r.media === null);
|
|
3577
|
+
const byMedia = /* @__PURE__ */ new Map();
|
|
3578
|
+
for (const r of rules) {
|
|
3579
|
+
if (r.media === null) continue;
|
|
3580
|
+
const list = byMedia.get(r.media) ?? [];
|
|
3581
|
+
list.push(r);
|
|
3582
|
+
byMedia.set(r.media, list);
|
|
3583
|
+
}
|
|
3584
|
+
const parts = [];
|
|
3585
|
+
for (const r of base) parts.push(r.text);
|
|
3586
|
+
for (const [media, list] of byMedia) {
|
|
3587
|
+
const inner = list.map((r) => r.text.replace(/^/gm, " ")).join("\n");
|
|
3588
|
+
parts.push(`@media ${media} {
|
|
3589
|
+
${inner}
|
|
3590
|
+
}`);
|
|
3591
|
+
}
|
|
3592
|
+
return parts.join("\n\n");
|
|
3593
|
+
}
|
|
3594
|
+
function planDeMirror(input) {
|
|
3595
|
+
const { css, components, options = {} } = input;
|
|
3596
|
+
const warningSampleLimit = options.warningSampleLimit ?? 40;
|
|
3597
|
+
const compClasses = components.map((c) => ({ name: c.name, set: new Set(c.classes) }));
|
|
3598
|
+
const rules = captureClassRules(css);
|
|
3599
|
+
const perComponent = /* @__PURE__ */ new Map();
|
|
3600
|
+
const sharedSelectors = [];
|
|
3601
|
+
const deadSelectors = [];
|
|
3602
|
+
let movedRules = 0;
|
|
3603
|
+
let movedBytes = 0;
|
|
3604
|
+
let sharedRules = 0;
|
|
3605
|
+
let deadRules = 0;
|
|
3606
|
+
let sharedBytes = 0;
|
|
3607
|
+
let deadBytes = 0;
|
|
3608
|
+
let totalBytes = 0;
|
|
3609
|
+
for (const rule of rules) {
|
|
3610
|
+
totalBytes += rule.bytes;
|
|
3611
|
+
const need = rule.subjectClasses.length > 0 ? rule.subjectClasses : rule.allClasses;
|
|
3612
|
+
const owners = [];
|
|
3613
|
+
for (const c of compClasses) {
|
|
3614
|
+
if (need.every((cls) => c.set.has(cls))) owners.push(c.name);
|
|
3615
|
+
}
|
|
3616
|
+
if (owners.length === 1) {
|
|
3617
|
+
const name = owners[0];
|
|
3618
|
+
const list = perComponent.get(name) ?? [];
|
|
3619
|
+
list.push(rule);
|
|
3620
|
+
perComponent.set(name, list);
|
|
3621
|
+
movedRules += 1;
|
|
3622
|
+
movedBytes += rule.bytes;
|
|
3623
|
+
} else if (owners.length === 0) {
|
|
3624
|
+
deadRules += 1;
|
|
3625
|
+
deadBytes += rule.bytes;
|
|
3626
|
+
if (deadSelectors.length < warningSampleLimit) deadSelectors.push(rule.selector);
|
|
3627
|
+
} else {
|
|
3628
|
+
sharedRules += 1;
|
|
3629
|
+
sharedBytes += rule.bytes;
|
|
3630
|
+
if (sharedSelectors.length < warningSampleLimit) sharedSelectors.push(rule.selector);
|
|
3631
|
+
}
|
|
3632
|
+
}
|
|
3633
|
+
const componentsOut = [];
|
|
3634
|
+
for (const [name, list] of perComponent) {
|
|
3635
|
+
const breakpoints = [...new Set(list.map((r) => r.media ?? ""))].sort();
|
|
3636
|
+
componentsOut.push({
|
|
3637
|
+
name,
|
|
3638
|
+
ruleCount: list.length,
|
|
3639
|
+
declarations: list.reduce((n, r) => n + r.declarations, 0),
|
|
3640
|
+
approxBytes: list.reduce((n, r) => n + r.bytes, 0),
|
|
3641
|
+
breakpoints,
|
|
3642
|
+
css: assembleSlice(list)
|
|
3643
|
+
});
|
|
3644
|
+
}
|
|
3645
|
+
componentsOut.sort((a, b) => b.approxBytes - a.approxBytes || a.name.localeCompare(b.name));
|
|
3646
|
+
const roundTrip = movedRules + sharedRules + deadRules === rules.length;
|
|
3647
|
+
return {
|
|
3648
|
+
source: { classRules: rules.length, classRuleBytes: totalBytes, componentCount: components.length },
|
|
3649
|
+
components: componentsOut,
|
|
3650
|
+
global: { sharedRules, deadRules, approxBytes: sharedBytes + deadBytes },
|
|
3651
|
+
moved: { rules: movedRules, approxBytes: movedBytes, components: componentsOut.length },
|
|
3652
|
+
warnings: { sharedSelectors, deadSelectors },
|
|
3653
|
+
roundTrip
|
|
3654
|
+
};
|
|
3655
|
+
}
|
|
3656
|
+
function renderDeMirrorMarkdown(plan) {
|
|
3657
|
+
const kb = (n) => `${(n / 1024).toFixed(1)} KB`;
|
|
3658
|
+
const lines = [];
|
|
3659
|
+
lines.push("# De-mirror plan");
|
|
3660
|
+
lines.push("");
|
|
3661
|
+
lines.push(
|
|
3662
|
+
`${plan.source.classRules} class rules (${kb(plan.source.classRuleBytes)}) across ${plan.source.componentCount} components \xB7 round-trip ${plan.roundTrip ? "\u2705 lossless" : "\u274C FAILED"}`
|
|
3663
|
+
);
|
|
3664
|
+
lines.push("");
|
|
3665
|
+
lines.push(
|
|
3666
|
+
`**Moves onto components:** ${plan.moved.rules} rules \u2192 ${plan.moved.components} components (${kb(plan.moved.approxBytes)})`
|
|
3667
|
+
);
|
|
3668
|
+
lines.push(
|
|
3669
|
+
`**Stays global:** ${plan.global.sharedRules} shared + ${plan.global.deadRules} dead = ${kb(plan.global.approxBytes)}`
|
|
3670
|
+
);
|
|
3671
|
+
lines.push("");
|
|
3672
|
+
lines.push("## Slices (largest first)");
|
|
3673
|
+
lines.push("");
|
|
3674
|
+
lines.push("| component | rules | declarations | ~size | breakpoints |");
|
|
3675
|
+
lines.push("| --- | --- | --- | --- | --- |");
|
|
3676
|
+
for (const c of plan.components.slice(0, 40)) {
|
|
3677
|
+
lines.push(`| ${c.name} | ${c.ruleCount} | ${c.declarations} | ${kb(c.approxBytes)} | ${c.breakpoints.length} |`);
|
|
3678
|
+
}
|
|
3679
|
+
if (plan.components.length > 40) lines.push(`| \u2026 +${plan.components.length - 40} more | | | | |`);
|
|
3680
|
+
lines.push("");
|
|
3681
|
+
lines.push(
|
|
3682
|
+
`_Shared (kept global): ${plan.warnings.sharedSelectors.slice(0, 6).join(", ") || "none"}${plan.warnings.sharedSelectors.length > 6 ? " \u2026" : ""}_`
|
|
3683
|
+
);
|
|
3684
|
+
return lines.join("\n");
|
|
3685
|
+
}
|
|
3686
|
+
|
|
3687
|
+
// lib/server/routes/api/deMirror.ts
|
|
3688
|
+
function componentsUsedByPage(pageData, allNames) {
|
|
3689
|
+
const hay = JSON.stringify(pageData ?? "");
|
|
3690
|
+
const used = /* @__PURE__ */ new Set();
|
|
3691
|
+
for (const name of allNames) {
|
|
3692
|
+
if (hay.includes(`"${name}"`)) used.add(name);
|
|
3693
|
+
}
|
|
3694
|
+
return used;
|
|
3695
|
+
}
|
|
3696
|
+
function handleDeMirrorRoute(url, pageService, componentService) {
|
|
3697
|
+
const root = getProjectRoot();
|
|
3698
|
+
const cssParam = url.searchParams.get("css");
|
|
3699
|
+
let cssFiles;
|
|
3700
|
+
if (cssParam) {
|
|
3701
|
+
const resolved = resolveCssParam(root, cssParam);
|
|
3702
|
+
if (!resolved) {
|
|
3703
|
+
return jsonResponse({ error: `Stylesheet not found or outside project: ${cssParam}` }, { status: 404 });
|
|
3704
|
+
}
|
|
3705
|
+
cssFiles = [resolved];
|
|
3706
|
+
} else {
|
|
3707
|
+
cssFiles = findMirrorCss(root);
|
|
3708
|
+
if (cssFiles.length === 0) {
|
|
3709
|
+
return jsonResponse(
|
|
3710
|
+
{
|
|
3711
|
+
error: "No mirror stylesheet found",
|
|
3712
|
+
hint: "Expected a Webflow export at public/_mirror/css/*.css. Pass ?css=<relative path> to target a specific stylesheet."
|
|
3713
|
+
},
|
|
3714
|
+
{ status: 404 }
|
|
3715
|
+
);
|
|
3716
|
+
}
|
|
3717
|
+
}
|
|
3718
|
+
const css = cssFiles.map((f) => readFileSync3(f, "utf8")).join("\n");
|
|
3719
|
+
const all = componentService.getAllComponents();
|
|
3720
|
+
const components = Object.entries(all).map(([name, def]) => ({
|
|
3721
|
+
name,
|
|
3722
|
+
classes: collectClassesFromPageData(def)
|
|
3723
|
+
}));
|
|
3724
|
+
if (components.length === 0) {
|
|
3725
|
+
return jsonResponse(
|
|
3726
|
+
{
|
|
3727
|
+
error: "No components found",
|
|
3728
|
+
hint: "Componentize the project first (e.g. /componentize-mirror), then de-mirror."
|
|
3729
|
+
},
|
|
3730
|
+
{ status: 422 }
|
|
3731
|
+
);
|
|
3732
|
+
}
|
|
3733
|
+
const plan = planDeMirror({ css, components });
|
|
3734
|
+
const slug = url.searchParams.get("slug");
|
|
3735
|
+
let scoped = plan.components;
|
|
3736
|
+
let scopedNote;
|
|
3737
|
+
if (slug) {
|
|
3738
|
+
const pagePath = slug === "index" ? "/" : `/${slug.replace(/^\//, "")}`;
|
|
3739
|
+
const pageData = pageService.getPageData(pagePath);
|
|
3740
|
+
if (!pageData) {
|
|
3741
|
+
return jsonResponse({ error: `Page not found: ${slug}` }, { status: 404 });
|
|
3742
|
+
}
|
|
3743
|
+
const used = componentsUsedByPage(
|
|
3744
|
+
pageData,
|
|
3745
|
+
components.map((c) => c.name)
|
|
3746
|
+
);
|
|
3747
|
+
scoped = plan.components.filter((c) => used.has(c.name));
|
|
3748
|
+
scopedNote = `filtered to ${scoped.length} component(s) used by /${slug} (ownership computed project-wide)`;
|
|
3749
|
+
}
|
|
3750
|
+
const cssFilesRel = cssFiles.map((f) => path2.relative(root, f));
|
|
3751
|
+
const view = { ...plan, components: scoped };
|
|
3752
|
+
const format = url.searchParams.get("format");
|
|
3753
|
+
if (format === "md" || format === "markdown") {
|
|
3754
|
+
const md = `${renderDeMirrorMarkdown(view)}
|
|
3755
|
+
${scopedNote ? `
|
|
3756
|
+
_${scopedNote}_
|
|
3757
|
+
` : ""}
|
|
3758
|
+
_Sheet: ${cssFilesRel.join(", ")}_
|
|
3759
|
+
`;
|
|
3760
|
+
return new Response(md, {
|
|
3761
|
+
headers: { "Content-Type": "text/markdown; charset=utf-8", "Cache-Control": "no-store, max-age=0" }
|
|
3762
|
+
});
|
|
3763
|
+
}
|
|
3764
|
+
const includeSlices = url.searchParams.get("slices") === "true";
|
|
3765
|
+
const responseComponents = includeSlices ? scoped : scoped.map(({ css: _css, ...meta }) => meta);
|
|
3766
|
+
return jsonResponse({
|
|
3767
|
+
...view,
|
|
3768
|
+
components: responseComponents,
|
|
3769
|
+
cssFiles: cssFilesRel,
|
|
3770
|
+
...scopedNote ? { scopedNote } : {},
|
|
3771
|
+
dryRun: true
|
|
3772
|
+
});
|
|
3773
|
+
}
|
|
3774
|
+
|
|
3775
|
+
// lib/server/routes/api/functions.ts
|
|
3776
|
+
import path3 from "node:path";
|
|
2885
3777
|
async function loadProjectEnv() {
|
|
2886
3778
|
const envPath = projectPaths.env();
|
|
2887
3779
|
const env = {};
|
|
@@ -2915,7 +3807,7 @@ async function handleFunctionsRoute(req, url) {
|
|
|
2915
3807
|
return void 0;
|
|
2916
3808
|
}
|
|
2917
3809
|
const functionPath = url.pathname.replace("/api/", "");
|
|
2918
|
-
const functionFilePath =
|
|
3810
|
+
const functionFilePath = path3.join(projectPaths.functions(), "api", `${functionPath}.ts`);
|
|
2919
3811
|
if (!await fileExists(functionFilePath)) {
|
|
2920
3812
|
const jsFilePath = functionFilePath.replace(".ts", ".js");
|
|
2921
3813
|
if (!await fileExists(jsFilePath)) {
|
|
@@ -3072,6 +3964,18 @@ async function handleCoreApiRoutes(req, url, context) {
|
|
|
3072
3964
|
if (url.pathname === "/api/enums" && req.method === "GET") {
|
|
3073
3965
|
return await handleRouteError(() => handleEnumsRoute(), "Failed to fetch enums");
|
|
3074
3966
|
}
|
|
3967
|
+
if (url.pathname === "/api/css-audit" && req.method === "GET") {
|
|
3968
|
+
return await handleRouteError(
|
|
3969
|
+
() => Promise.resolve(handleCssAuditRoute(url, pageService)),
|
|
3970
|
+
"Failed to generate CSS audit"
|
|
3971
|
+
);
|
|
3972
|
+
}
|
|
3973
|
+
if (url.pathname === "/api/de-mirror" && req.method === "GET") {
|
|
3974
|
+
return await handleRouteError(
|
|
3975
|
+
() => Promise.resolve(handleDeMirrorRoute(url, pageService, componentService)),
|
|
3976
|
+
"Failed to generate de-mirror plan"
|
|
3977
|
+
);
|
|
3978
|
+
}
|
|
3075
3979
|
if (url.pathname === "/api/usage" && req.method === "GET") {
|
|
3076
3980
|
return await handleRouteError(() => {
|
|
3077
3981
|
const cmsCollections = cmsService ? cmsService.getAllSchemas().size : 0;
|
|
@@ -3647,21 +4551,21 @@ function processCMSPropsTemplate(props, cmsItem, locale, i18nConfig) {
|
|
|
3647
4551
|
}
|
|
3648
4552
|
|
|
3649
4553
|
// lib/server/ssr/imageMetadata.ts
|
|
3650
|
-
import * as
|
|
4554
|
+
import * as path4 from "node:path";
|
|
3651
4555
|
var RESPONSIVE_WIDTHS = [500, 800, 1080, 1600, 2400];
|
|
3652
4556
|
var DEFAULT_SIZES = "100vw";
|
|
3653
4557
|
async function buildImageMetadataMap() {
|
|
3654
4558
|
const metadataMap = /* @__PURE__ */ new Map();
|
|
3655
4559
|
try {
|
|
3656
4560
|
const imagesDir = projectPaths.images();
|
|
3657
|
-
const manifestPath =
|
|
4561
|
+
const manifestPath = path4.join(imagesDir, "manifest.json");
|
|
3658
4562
|
if (!await fileExists(manifestPath)) {
|
|
3659
4563
|
return metadataMap;
|
|
3660
4564
|
}
|
|
3661
4565
|
const manifestContent = await readTextFile(manifestPath);
|
|
3662
4566
|
const manifest = JSON.parse(manifestContent);
|
|
3663
4567
|
for (const [filename, entry] of Object.entries(manifest.images)) {
|
|
3664
|
-
const baseName =
|
|
4568
|
+
const baseName = path4.basename(filename, path4.extname(filename));
|
|
3665
4569
|
const webpSrcsetParts = [];
|
|
3666
4570
|
const avifSrcsetParts = [];
|
|
3667
4571
|
for (const variant of entry.variants) {
|
|
@@ -3839,8 +4743,8 @@ function resolveFilterValue(value, scope) {
|
|
|
3839
4743
|
if (typeof value !== "string" || !value.startsWith("{{") || !value.endsWith("}}")) {
|
|
3840
4744
|
return value;
|
|
3841
4745
|
}
|
|
3842
|
-
const
|
|
3843
|
-
const resolved = scope ? getNestedValue(scope,
|
|
4746
|
+
const path5 = value.slice(2, -2).trim();
|
|
4747
|
+
const resolved = scope ? getNestedValue(scope, path5) : void 0;
|
|
3844
4748
|
return resolved !== void 0 ? resolved : UNRESOLVED_FILTER_VALUE;
|
|
3845
4749
|
}
|
|
3846
4750
|
function resolveFilterTemplates(filter, scope, collection) {
|
|
@@ -4250,8 +5154,8 @@ function getPropItems(source, ctx) {
|
|
|
4250
5154
|
if (source.startsWith("{{") && source.endsWith("}}")) {
|
|
4251
5155
|
const templateCtx = ctx.templateContext;
|
|
4252
5156
|
if (templateCtx) {
|
|
4253
|
-
const
|
|
4254
|
-
const resolved = getNestedValue(templateCtx,
|
|
5157
|
+
const path5 = source.slice(2, -2).trim();
|
|
5158
|
+
const resolved = getNestedValue(templateCtx, path5);
|
|
4255
5159
|
if (Array.isArray(resolved)) {
|
|
4256
5160
|
return resolved;
|
|
4257
5161
|
} else if (resolved !== void 0) {
|
|
@@ -4288,12 +5192,12 @@ function buildNodeElementClass(ctx, label, isSlotContent2) {
|
|
|
4288
5192
|
const effectiveFileType = useComponentContext ? "component" : "page";
|
|
4289
5193
|
const effectiveFileName = useComponentContext ? ctx.componentContext : pagePath ? pagePath.replace(/^\//, "").replace(/\//g, "_") || "index" : "page";
|
|
4290
5194
|
const rawPath = ctx.elementPath || [];
|
|
4291
|
-
const
|
|
5195
|
+
const path5 = useComponentContext && ctx.componentRootPath ? rawPath.slice(ctx.componentRootPath.length) : rawPath;
|
|
4292
5196
|
const elementClassCtx = {
|
|
4293
5197
|
fileType: effectiveFileType,
|
|
4294
5198
|
fileName: effectiveFileName || "page",
|
|
4295
5199
|
label,
|
|
4296
|
-
path:
|
|
5200
|
+
path: path5
|
|
4297
5201
|
};
|
|
4298
5202
|
return generateElementClassName(elementClassCtx);
|
|
4299
5203
|
}
|
|
@@ -5454,6 +6358,7 @@ function generateAllInlineDataScripts(collections) {
|
|
|
5454
6358
|
}
|
|
5455
6359
|
|
|
5456
6360
|
// lib/server/ssr/htmlGenerator.ts
|
|
6361
|
+
import { posix } from "node:path";
|
|
5457
6362
|
function generateImagePreloadTags(preloadImages) {
|
|
5458
6363
|
if (preloadImages.length === 0) return "";
|
|
5459
6364
|
return preloadImages.map(
|
|
@@ -5464,6 +6369,43 @@ function minifyCSS(code) {
|
|
|
5464
6369
|
if (!code.trim()) return code;
|
|
5465
6370
|
return code.replace(/\/\*[\s\S]*?\*\//g, "").replace(/\s*([{};:,>~])\s*/g, "$1").replace(/\s+/g, " ").replace(/\{\s+/g, "{").replace(/\s+\}/g, "}").replace(/;}/g, "}").trim();
|
|
5466
6371
|
}
|
|
6372
|
+
async function collectPageCssImports(frontmatter, pagePath, nonceAttr) {
|
|
6373
|
+
if (!frontmatter?.includes(".css")) return "";
|
|
6374
|
+
const seen = /* @__PURE__ */ new Set();
|
|
6375
|
+
const specs = [];
|
|
6376
|
+
const re = /import\b[^'"]*['"]([^'"]+\.css)(?:\?[^'"]*)?['"]/g;
|
|
6377
|
+
let m;
|
|
6378
|
+
while ((m = re.exec(frontmatter)) !== null) {
|
|
6379
|
+
const spec = m[1];
|
|
6380
|
+
if (!spec || seen.has(spec)) continue;
|
|
6381
|
+
seen.add(spec);
|
|
6382
|
+
if (spec.endsWith("styles/theme.css")) continue;
|
|
6383
|
+
specs.push(spec);
|
|
6384
|
+
}
|
|
6385
|
+
if (specs.length === 0) return "";
|
|
6386
|
+
const pageDir = posix.join("src/pages", posix.dirname(pagePath || "/"));
|
|
6387
|
+
const tags = [];
|
|
6388
|
+
for (const spec of specs) {
|
|
6389
|
+
if (/^(?:https?:)?\/\//.test(spec)) {
|
|
6390
|
+
tags.push(`<link rel="stylesheet" href="${escapeHtml(spec)}">`);
|
|
6391
|
+
continue;
|
|
6392
|
+
}
|
|
6393
|
+
const candidates = spec.startsWith("/") ? [spec.slice(1)] : [posix.normalize(posix.join(pageDir, spec)), `src/${spec.replace(/^(?:\.\.?\/)+/, "")}`];
|
|
6394
|
+
let css = null;
|
|
6395
|
+
for (const rel of candidates) {
|
|
6396
|
+
if (rel.startsWith("..")) continue;
|
|
6397
|
+
try {
|
|
6398
|
+
css = await readTextFile(resolveProjectPath(rel));
|
|
6399
|
+
break;
|
|
6400
|
+
} catch {
|
|
6401
|
+
}
|
|
6402
|
+
}
|
|
6403
|
+
if (css == null) continue;
|
|
6404
|
+
const content = rewriteViewportUnitsInStylesheet(css).replace(/<\/style/gi, "<\\/style");
|
|
6405
|
+
tags.push(`<style${nonceAttr} data-meno-page-css="${escapeHtml(spec)}">${content}</style>`);
|
|
6406
|
+
}
|
|
6407
|
+
return tags.join("\n ");
|
|
6408
|
+
}
|
|
5467
6409
|
async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePath = "/", baseUrl = "", useBuiltBundle = false, locale, slugMappings, cmsContext, cmsService, externalScriptPath) {
|
|
5468
6410
|
let options;
|
|
5469
6411
|
if ("pageData" in pageDataOrOptions) {
|
|
@@ -5485,7 +6427,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5485
6427
|
const {
|
|
5486
6428
|
pageData,
|
|
5487
6429
|
globalComponents: components = {},
|
|
5488
|
-
pagePath:
|
|
6430
|
+
pagePath: path5 = "/",
|
|
5489
6431
|
baseUrl: base = "",
|
|
5490
6432
|
useBuiltBundle: useBundled = false,
|
|
5491
6433
|
locale: loc,
|
|
@@ -5509,7 +6451,7 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5509
6451
|
const rendered = await renderPageSSR(
|
|
5510
6452
|
pageData,
|
|
5511
6453
|
components,
|
|
5512
|
-
|
|
6454
|
+
path5,
|
|
5513
6455
|
base,
|
|
5514
6456
|
loc,
|
|
5515
6457
|
void 0,
|
|
@@ -5546,11 +6488,10 @@ async function generateSSRHTML(pageDataOrOptions, globalComponents = {}, pagePat
|
|
|
5546
6488
|
await configService.load();
|
|
5547
6489
|
const globalLibraries = configService.getLibraries() || { js: [], css: [] };
|
|
5548
6490
|
const globalCustomCode = configService.getCustomCode();
|
|
5549
|
-
const menoBadgeHtml = configService.getShowMenoBadge() ? `<style${nonceAttr}>.meno-badge{position:fixed;bottom:12px;left:12px;z-index:9999;background:#000;color:#fff;padding:4px 10px;border-radius:6px;font-size:12px;font-family:system-ui,sans-serif;text-decoration:none;opacity:0.8;transition:opacity 0.2s}.meno-badge:hover,.meno-badge:focus{opacity:1}</style><a class="meno-badge" href="https://meno.so" target="_blank" rel="noopener">Made in Meno</a>` : "";
|
|
5550
6491
|
const mergedCustomCode = {
|
|
5551
6492
|
head: [globalCustomCode.head, pageCustomCode?.head].filter(Boolean).join("\n"),
|
|
5552
6493
|
bodyStart: [globalCustomCode.bodyStart, pageCustomCode?.bodyStart].filter(Boolean).join("\n"),
|
|
5553
|
-
bodyEnd: [globalCustomCode.bodyEnd, pageCustomCode?.bodyEnd
|
|
6494
|
+
bodyEnd: [globalCustomCode.bodyEnd, pageCustomCode?.bodyEnd].filter(Boolean).join("\n")
|
|
5554
6495
|
};
|
|
5555
6496
|
const componentLibraries = collectComponentLibraries(components, pageData.components || {});
|
|
5556
6497
|
const globalPlusComponent = mergeLibraries(globalLibraries, componentLibraries) || { js: [], css: [] };
|
|
@@ -5696,7 +6637,7 @@ picture {
|
|
|
5696
6637
|
line-height: normal;
|
|
5697
6638
|
}`;
|
|
5698
6639
|
const combinedCSS = [fontCSS, themeColorVariablesCSS, variablesCSS, baseCSS, componentCSS, utilityCSS, interactiveCSS].filter(Boolean).join("\n");
|
|
5699
|
-
const cssWithStableViewport =
|
|
6640
|
+
const cssWithStableViewport = rewriteViewportUnitsInStylesheet(combinedCSS);
|
|
5700
6641
|
const finalCSS = useBundled ? minifyCSS(cssWithStableViewport) : cssWithStableViewport;
|
|
5701
6642
|
const prefetchConfig = await loadPrefetchConfig();
|
|
5702
6643
|
const menoConfig = prefetchConfig.enabled ? { prefetch: prefetchConfig } : {};
|
|
@@ -5720,11 +6661,12 @@ ${externalJavaScript}`;
|
|
|
5720
6661
|
const scriptPreloadTag = extScriptPath ? `<link rel="preload" href="${extScriptPath}" as="script">` : "";
|
|
5721
6662
|
const imagePreloadTags = generateImagePreloadTags(rendered.preloadImages);
|
|
5722
6663
|
const wsUrl = serverPort ? `'ws://localhost:${serverPort}/hmr'` : `location.origin.replace('http','ws')+'/hmr'`;
|
|
5723
|
-
const liveReloadScript = injectLiveReload ? `<script${nonceAttr}>(function(){var ws,timer,gen=0,lastSrvRoot=null,dn=document.querySelector('meta[name="csp-nonce"]'),docNonce=dn?dn.getAttribute('content'):'';function strip(s){return s?s.replace(/[?&]_r=\\d+/,''):''}function classList(el){return (el.getAttribute('class')||'').split(/\\s+/).filter(Boolean)}function syncEl(cur,srv,old){var cc=classList(cur),sc=classList(srv),oc=old?new Set(classList(old)):new Set();var rt=cc.filter(function(c){return !oc.has(c)});var seen=new Set(),fin=[];sc.concat(rt).forEach(function(c){if(!seen.has(c)){seen.add(c);fin.push(c)}});var fs=fin.join(' ');if((cur.getAttribute('class')||'')!==fs){if(fs)cur.setAttribute('class',fs);else cur.removeAttribute('class')}for(var i=0;i<srv.attributes.length;i++){var a=srv.attributes[i];if(a.name==='class')continue;var ov=old?old.getAttribute(a.name):null;if(a.value!==ov){if(cur.getAttribute(a.name)!==a.value)cur.setAttribute(a.name,a.value)}}if(old){for(var i=0;i<old.attributes.length;i++){var a=old.attributes[i];if(a.name==='class')continue;if(!srv.hasAttribute(a.name)&&cur.hasAttribute(a.name))cur.removeAttribute(a.name)}}}function syncText(cur,srv){var cc=cur.childNodes,sc=srv.childNodes;for(var i=0;i<sc.length;i++){var s=sc[i],c=cc[i];if(s.nodeType===3&&c&&c.nodeType===3){if(c.textContent!==s.textContent)c.textContent=s.textContent}}}function ek(el){var p=el.getAttribute('data-element-path'),ci=el.getAttribute('data-cms-item-index');return ci?p+'|'+ci:p}function structKey(root){var e=root.querySelectorAll('[data-element-path]'),k=[];for(var i=0;i<e.length;i++)k.push(ek(e[i]));return k.sort().join('~')}function softSync(curR,srvR,oldR){var ce=curR.querySelectorAll('[data-element-path]');var sbp={},se=srvR.querySelectorAll('[data-element-path]');for(var i=0;i<se.length;i++)sbp[ek(se[i])]=se[i];var obp={};if(oldR){var oe=oldR.querySelectorAll('[data-element-path]');for(var i=0;i<oe.length;i++)obp[ek(oe[i])]=oe[i]}for(var i=0;i<ce.length;i++){var c=ce[i],p=ek(c),s=sbp[p];if(!s)continue;syncEl(c,s,obp[p]);syncText(c,s)}syncText(curR,srvR)}function connect(){ws=new WebSocket(${wsUrl});ws.onmessage=function(e){var d=JSON.parse(e.data);if(d.type==='hmr:libraries-update'){location.reload()}else if(d.type==='hmr:update'||d.type==='hmr:cms-update'||d.type==='hmr:colors-update'||d.type==='hmr:variables-update')hotReload()};ws.onclose=function(){clearTimeout(timer);timer=setTimeout(connect,1000)}}function hotReload(){var g=++gen;var sx=window.scrollX,sy=window.scrollY;fetch(location.href,{cache:'no-store'}).then(function(r){return r.text()}).then(function(html){if(g!==gen)return;var p=new DOMParser();var d=p.parseFromString(html,'text/html');var or=document.getElementById('root'),nr=d.getElementById('root');var oscr=document.querySelector('script[src^="/_scripts/"]'),nscr=d.querySelector('script[src^="/_scripts/"]');var oss=oscr?strip(oscr.getAttribute('src')):'',nss=nscr?strip(nscr.getAttribute('src')):'';var hardReset=(oss!==nss)||!lastSrvRoot||!or||!nr||structKey(lastSrvRoot)!==structKey(nr);if(or&&nr){if(hardReset){if(or.innerHTML!==nr.innerHTML)or.innerHTML=nr.innerHTML}else{softSync(or,nr,lastSrvRoot)}}if(nr)lastSrvRoot=nr.cloneNode(true);var os=document.getElementById('meno-styles'),ns=d.getElementById('meno-styles');if(os&&ns&&os.textContent!==ns.textContent)os.textContent=ns.textContent;var nh=d.documentElement;if(nh){var nl=nh.getAttribute('lang')||'en',nt=nh.getAttribute('theme')||'light';if(document.documentElement.getAttribute('lang')!==nl)document.documentElement.setAttribute('lang',nl);if(document.documentElement.getAttribute('theme')!==nt)document.documentElement.setAttribute('theme',nt)}var ocms=document.querySelectorAll('script[id^="meno-cms-"]'),ncms=d.querySelectorAll('script[id^="meno-cms-"]');var ock=JSON.stringify(Array.prototype.map.call(ocms,function(s){return [s.id,s.textContent]}));var nck=JSON.stringify(Array.prototype.map.call(ncms,function(s){return [s.id,s.textContent]}));if(ock!==nck){ocms.forEach(function(s){s.remove()});ncms.forEach(function(s){var c=document.createElement('script');c.type=s.type;c.id=s.id;if(docNonce)c.nonce=docNonce;c.textContent=s.textContent;document.head.appendChild(c)})}window.__menoHotReload=true;var olib=document.querySelectorAll('body > script[src^="/libraries/"]'),nlib=d.querySelectorAll('body > script[src^="/libraries/"]');var olk=JSON.stringify(Array.prototype.map.call(olib,function(s){return strip(s.getAttribute('src'))}).sort());var nlk=JSON.stringify(Array.prototype.map.call(nlib,function(s){return strip(s.getAttribute('src'))}).sort());if(olk!==nlk){olib.forEach(function(o){o.remove()});nlib.forEach(function(n){var src=n.getAttribute('src');var ls=document.createElement('script');ls.src=src+(src.indexOf('?')>-1?'&':'?')+'_r='+Date.now();document.body.appendChild(ls)})}if(!hardReset){
|
|
5724
|
-
const scrollHandlerScript = injectLiveReload ? `<script${nonceAttr}>(function(){window.addEventListener('message',function(e){if(e.data.type==='GET_SCROLL_POSITION'){window.parent.postMessage({type:'SCROLL_POSITION_RESPONSE',scrollX:window.scrollX,scrollY:window.scrollY},'*')}else if(e.data.type==='SET_SCROLL_POSITION'){window.scrollTo(e.data.scrollX,e.data.scrollY)}})})()</script>` : "";
|
|
6664
|
+
const liveReloadScript = injectLiveReload ? `<script${nonceAttr}>(function(){var ws,timer,gen=0,lastSrvRoot=null,dn=document.querySelector('meta[name="csp-nonce"]'),docNonce=dn?dn.getAttribute('content'):'';function strip(s){return s?s.replace(/[?&]_r=\\d+/,''):''}function iScroll(x,y){var de=document.documentElement,b=document.body,pd=de.style.scrollBehavior,pb=b?b.style.scrollBehavior:'';de.style.scrollBehavior='auto';if(b)b.style.scrollBehavior='auto';try{window.scrollTo(x,y)}catch(e){}de.style.scrollBehavior=pd;if(b)b.style.scrollBehavior=pb}function classList(el){return (el.getAttribute('class')||'').split(/\\s+/).filter(Boolean)}function syncEl(cur,srv,old){var cc=classList(cur),sc=classList(srv),oc=old?new Set(classList(old)):new Set();var rt=cc.filter(function(c){return !oc.has(c)});var seen=new Set(),fin=[];sc.concat(rt).forEach(function(c){if(!seen.has(c)){seen.add(c);fin.push(c)}});var fs=fin.join(' ');if((cur.getAttribute('class')||'')!==fs){if(fs)cur.setAttribute('class',fs);else cur.removeAttribute('class')}for(var i=0;i<srv.attributes.length;i++){var a=srv.attributes[i];if(a.name==='class')continue;var ov=old?old.getAttribute(a.name):null;if(a.value!==ov){if(cur.getAttribute(a.name)!==a.value)cur.setAttribute(a.name,a.value)}}if(old){for(var i=0;i<old.attributes.length;i++){var a=old.attributes[i];if(a.name==='class')continue;if(!srv.hasAttribute(a.name)&&cur.hasAttribute(a.name))cur.removeAttribute(a.name)}}}function syncText(cur,srv){var cc=cur.childNodes,sc=srv.childNodes;for(var i=0;i<sc.length;i++){var s=sc[i],c=cc[i];if(s.nodeType===3&&c&&c.nodeType===3){if(c.textContent!==s.textContent)c.textContent=s.textContent}}}function ek(el){var p=el.getAttribute('data-element-path'),ci=el.getAttribute('data-cms-item-index');return ci?p+'|'+ci:p}function structKey(root){var e=root.querySelectorAll('[data-element-path]'),k=[];for(var i=0;i<e.length;i++)k.push(ek(e[i]));return k.sort().join('~')}function softSync(curR,srvR,oldR){var ce=curR.querySelectorAll('[data-element-path]');var sbp={},se=srvR.querySelectorAll('[data-element-path]');for(var i=0;i<se.length;i++)sbp[ek(se[i])]=se[i];var obp={};if(oldR){var oe=oldR.querySelectorAll('[data-element-path]');for(var i=0;i<oe.length;i++)obp[ek(oe[i])]=oe[i]}for(var i=0;i<ce.length;i++){var c=ce[i],p=ek(c),s=sbp[p];if(!s)continue;syncEl(c,s,obp[p]);syncText(c,s)}syncText(curR,srvR)}function connect(){ws=new WebSocket(${wsUrl});ws.onmessage=function(e){var d=JSON.parse(e.data);if(d.type==='hmr:libraries-update'){location.reload()}else if(d.type==='hmr:update'||d.type==='hmr:cms-update'||d.type==='hmr:colors-update'||d.type==='hmr:variables-update')hotReload()};ws.onclose=function(){clearTimeout(timer);timer=setTimeout(connect,1000)}}function hotReload(){var g=++gen;var sx=window.scrollX,sy=window.scrollY;fetch(location.href,{cache:'no-store'}).then(function(r){return r.text()}).then(function(html){if(g!==gen)return;var p=new DOMParser();var d=p.parseFromString(html,'text/html');var or=document.getElementById('root'),nr=d.getElementById('root');var oscr=document.querySelector('script[src^="/_scripts/"]'),nscr=d.querySelector('script[src^="/_scripts/"]');var oss=oscr?strip(oscr.getAttribute('src')):'',nss=nscr?strip(nscr.getAttribute('src')):'';var hardReset=(oss!==nss)||!lastSrvRoot||!or||!nr||structKey(lastSrvRoot)!==structKey(nr);if(or&&nr){if(hardReset){if(or.innerHTML!==nr.innerHTML)or.innerHTML=nr.innerHTML}else{softSync(or,nr,lastSrvRoot)}}if(nr)lastSrvRoot=nr.cloneNode(true);var os=document.getElementById('meno-styles'),ns=d.getElementById('meno-styles');if(os&&ns&&os.textContent!==ns.textContent)os.textContent=ns.textContent;var nh=d.documentElement;if(nh){var nl=nh.getAttribute('lang')||'en',nt=nh.getAttribute('theme')||'light';if(document.documentElement.getAttribute('lang')!==nl)document.documentElement.setAttribute('lang',nl);if(document.documentElement.getAttribute('theme')!==nt)document.documentElement.setAttribute('theme',nt)}var ocms=document.querySelectorAll('script[id^="meno-cms-"]'),ncms=d.querySelectorAll('script[id^="meno-cms-"]');var ock=JSON.stringify(Array.prototype.map.call(ocms,function(s){return [s.id,s.textContent]}));var nck=JSON.stringify(Array.prototype.map.call(ncms,function(s){return [s.id,s.textContent]}));if(ock!==nck){ocms.forEach(function(s){s.remove()});ncms.forEach(function(s){var c=document.createElement('script');c.type=s.type;c.id=s.id;if(docNonce)c.nonce=docNonce;c.textContent=s.textContent;document.head.appendChild(c)})}window.__menoHotReload=true;var olib=document.querySelectorAll('body > script[src^="/libraries/"]'),nlib=d.querySelectorAll('body > script[src^="/libraries/"]');var olk=JSON.stringify(Array.prototype.map.call(olib,function(s){return strip(s.getAttribute('src'))}).sort());var nlk=JSON.stringify(Array.prototype.map.call(nlib,function(s){return strip(s.getAttribute('src'))}).sort());if(olk!==nlk){olib.forEach(function(o){o.remove()});nlib.forEach(function(n){var src=n.getAttribute('src');var ls=document.createElement('script');ls.src=src+(src.indexOf('?')>-1?'&':'?')+'_r='+Date.now();document.body.appendChild(ls)})}if(!hardReset){iScroll(sx,sy)}else{if(oscr)oscr.remove();if(nscr){var src=nscr.getAttribute('src');var s=document.createElement('script');s.src=src+(src.indexOf('?')>-1?'&':'?')+'_r='+Date.now();s.onload=function(){document.dispatchEvent(new Event('DOMContentLoaded'));iScroll(sx,sy)};s.onerror=function(){iScroll(sx,sy)};document.body.appendChild(s)}else{document.dispatchEvent(new Event('DOMContentLoaded'));iScroll(sx,sy)}}}).catch(function(){location.reload()})}var iR=document.getElementById('root');if(iR)lastSrvRoot=iR.cloneNode(true);connect()})()</script>` : "";
|
|
6665
|
+
const scrollHandlerScript = injectLiveReload ? `<script${nonceAttr}>(function(){window.addEventListener('message',function(e){if(e.data.type==='GET_SCROLL_POSITION'){window.parent.postMessage({type:'SCROLL_POSITION_RESPONSE',scrollX:window.scrollX,scrollY:window.scrollY},'*')}else if(e.data.type==='SET_SCROLL_POSITION'){var de=document.documentElement,b=document.body,pd=de.style.scrollBehavior,pb=b?b.style.scrollBehavior:'';de.style.scrollBehavior='auto';if(b)b.style.scrollBehavior='auto';window.scrollTo(e.data.scrollX,e.data.scrollY);de.style.scrollBehavior=pd;if(b)b.style.scrollBehavior=pb}})})()</script>` : "";
|
|
5725
6666
|
const styleContent = useBundled ? finalCSS : `
|
|
5726
6667
|
${combinedCSS.split("\n").join("\n ")}
|
|
5727
6668
|
`;
|
|
6669
|
+
const pageCssTags = await collectPageCssImports(pageData._frontmatter, pagePath, nonceAttr);
|
|
5728
6670
|
const htmlDocument = `<!DOCTYPE html>
|
|
5729
6671
|
<html lang="${rendered.locale}" theme="${themeConfig.default}">
|
|
5730
6672
|
<head>
|
|
@@ -5738,7 +6680,8 @@ ${externalJavaScript}`;
|
|
|
5738
6680
|
` : ""}${libraryTags.headCSS ? `${libraryTags.headCSS}
|
|
5739
6681
|
` : ""}${libraryTags.headJS ? `${libraryTags.headJS}
|
|
5740
6682
|
` : ""}${rendered.meta}
|
|
5741
|
-
${configInlineScript}${cmsInlineScript}${clientDataScripts}<style id="meno-styles"${nonceAttr}>${styleContent}</style>${
|
|
6683
|
+
${configInlineScript}${cmsInlineScript}${clientDataScripts}<style id="meno-styles"${nonceAttr}>${styleContent}</style>${pageCssTags ? `
|
|
6684
|
+
${pageCssTags}` : ""}${mergedCustomCode.head ? `
|
|
5742
6685
|
${mergedCustomCode.head}` : ""}
|
|
5743
6686
|
</head>
|
|
5744
6687
|
<body>${mergedCustomCode.bodyStart ? `
|
|
@@ -7146,8 +8089,8 @@ function singularizeWord(word) {
|
|
|
7146
8089
|
if (word.endsWith("s")) return word.slice(0, -1);
|
|
7147
8090
|
return word;
|
|
7148
8091
|
}
|
|
7149
|
-
function getNestedValue2(obj,
|
|
7150
|
-
return
|
|
8092
|
+
function getNestedValue2(obj, path5) {
|
|
8093
|
+
return path5.split(".").reduce((current, key) => {
|
|
7151
8094
|
return current && typeof current === "object" ? current[key] : void 0;
|
|
7152
8095
|
}, obj);
|
|
7153
8096
|
}
|
|
@@ -8628,7 +9571,11 @@ async function createServer(config) {
|
|
|
8628
9571
|
try {
|
|
8629
9572
|
const server = await createRuntimeServer({
|
|
8630
9573
|
port: currentPort,
|
|
8631
|
-
|
|
9574
|
+
// Default `localhost` resolves to IPv6 `::1` only on macOS, so a
|
|
9575
|
+
// conventional `127.0.0.1 meno.local` hosts alias can't reach it. MENO_HOST
|
|
9576
|
+
// lets the standalone launcher bind broadly (e.g. `::` dual-stack) so both
|
|
9577
|
+
// IPv4 and IPv6 aliases work. Unset → unchanged `localhost` behavior.
|
|
9578
|
+
hostname: process.env.MENO_HOST || "localhost",
|
|
8632
9579
|
wsPath: HMR_ROUTE,
|
|
8633
9580
|
async fetch(req, upgradeWebSocket) {
|
|
8634
9581
|
const url = new URL(req.url);
|
|
@@ -9023,8 +9970,8 @@ ${plainTextErrors}`;
|
|
|
9023
9970
|
}
|
|
9024
9971
|
|
|
9025
9972
|
// lib/server/services/pageService.ts
|
|
9026
|
-
import { existsSync as
|
|
9027
|
-
import { join as join4 } from "node:path";
|
|
9973
|
+
import { existsSync as existsSync6, readdirSync as readdirSync3, mkdirSync, rmdirSync } from "node:fs";
|
|
9974
|
+
import { join as join4, relative, sep } from "node:path";
|
|
9028
9975
|
|
|
9029
9976
|
// lib/server/utils/jsonLineMapper.ts
|
|
9030
9977
|
function buildLineMap(jsonText) {
|
|
@@ -9068,13 +10015,13 @@ function buildLineMap(jsonText) {
|
|
|
9068
10015
|
pos++;
|
|
9069
10016
|
}
|
|
9070
10017
|
}
|
|
9071
|
-
function parseValue(
|
|
10018
|
+
function parseValue(path5, trackChildren) {
|
|
9072
10019
|
skipWhitespace();
|
|
9073
10020
|
const startPos = pos;
|
|
9074
10021
|
if (jsonText[pos] === "{") {
|
|
9075
|
-
parseObject(
|
|
10022
|
+
parseObject(path5, trackChildren);
|
|
9076
10023
|
} else if (jsonText[pos] === "[") {
|
|
9077
|
-
parseArray(
|
|
10024
|
+
parseArray(path5, trackChildren);
|
|
9078
10025
|
} else if (jsonText[pos] === '"') {
|
|
9079
10026
|
parseString();
|
|
9080
10027
|
} else if (jsonText[pos] === "t") {
|
|
@@ -9088,14 +10035,14 @@ function buildLineMap(jsonText) {
|
|
|
9088
10035
|
}
|
|
9089
10036
|
return { start: startPos, end: pos };
|
|
9090
10037
|
}
|
|
9091
|
-
function recordTrackedRoot(
|
|
9092
|
-
const { start, end } = parseValue(
|
|
10038
|
+
function recordTrackedRoot(path5) {
|
|
10039
|
+
const { start, end } = parseValue(path5, true);
|
|
9093
10040
|
lineMap.set("", {
|
|
9094
10041
|
startLine: charToLine[start] ?? 0,
|
|
9095
10042
|
endLine: charToLine[end - 1] || charToLine[start] || 0
|
|
9096
10043
|
});
|
|
9097
10044
|
}
|
|
9098
|
-
function parseObject(
|
|
10045
|
+
function parseObject(path5, trackChildren) {
|
|
9099
10046
|
pos++;
|
|
9100
10047
|
skipWhitespace();
|
|
9101
10048
|
while (pos < jsonText.length && jsonText[pos] !== "}") {
|
|
@@ -9104,14 +10051,14 @@ function buildLineMap(jsonText) {
|
|
|
9104
10051
|
skipWhitespace();
|
|
9105
10052
|
pos++;
|
|
9106
10053
|
skipWhitespace();
|
|
9107
|
-
if (key === "root" &&
|
|
9108
|
-
recordTrackedRoot(
|
|
9109
|
-
} else if (key === "component" &&
|
|
9110
|
-
parseComponentWrapper(
|
|
10054
|
+
if (key === "root" && path5.length === 0) {
|
|
10055
|
+
recordTrackedRoot(path5);
|
|
10056
|
+
} else if (key === "component" && path5.length === 0) {
|
|
10057
|
+
parseComponentWrapper(path5);
|
|
9111
10058
|
} else if (key === "children" && trackChildren) {
|
|
9112
|
-
parseValue(
|
|
10059
|
+
parseValue(path5, true);
|
|
9113
10060
|
} else {
|
|
9114
|
-
parseValue(
|
|
10061
|
+
parseValue(path5, false);
|
|
9115
10062
|
}
|
|
9116
10063
|
skipWhitespace();
|
|
9117
10064
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9119,9 +10066,9 @@ function buildLineMap(jsonText) {
|
|
|
9119
10066
|
}
|
|
9120
10067
|
pos++;
|
|
9121
10068
|
}
|
|
9122
|
-
function parseComponentWrapper(
|
|
10069
|
+
function parseComponentWrapper(path5) {
|
|
9123
10070
|
if (jsonText[pos] !== "{") {
|
|
9124
|
-
parseValue(
|
|
10071
|
+
parseValue(path5, false);
|
|
9125
10072
|
return;
|
|
9126
10073
|
}
|
|
9127
10074
|
pos++;
|
|
@@ -9133,9 +10080,9 @@ function buildLineMap(jsonText) {
|
|
|
9133
10080
|
pos++;
|
|
9134
10081
|
skipWhitespace();
|
|
9135
10082
|
if (key === "structure") {
|
|
9136
|
-
recordTrackedRoot(
|
|
10083
|
+
recordTrackedRoot(path5);
|
|
9137
10084
|
} else {
|
|
9138
|
-
parseValue(
|
|
10085
|
+
parseValue(path5, false);
|
|
9139
10086
|
}
|
|
9140
10087
|
skipWhitespace();
|
|
9141
10088
|
if (jsonText[pos] === ",") pos++;
|
|
@@ -9143,12 +10090,12 @@ function buildLineMap(jsonText) {
|
|
|
9143
10090
|
}
|
|
9144
10091
|
pos++;
|
|
9145
10092
|
}
|
|
9146
|
-
function parseArray(
|
|
10093
|
+
function parseArray(path5, trackChildren) {
|
|
9147
10094
|
pos++;
|
|
9148
10095
|
skipWhitespace();
|
|
9149
10096
|
let index = 0;
|
|
9150
10097
|
while (pos < jsonText.length && jsonText[pos] !== "]") {
|
|
9151
|
-
const childPath = [...
|
|
10098
|
+
const childPath = [...path5, index];
|
|
9152
10099
|
const { start, end } = parseValue(childPath, trackChildren);
|
|
9153
10100
|
if (trackChildren) {
|
|
9154
10101
|
const pathStr = childPath.join(",");
|
|
@@ -9221,9 +10168,12 @@ var PageService = class {
|
|
|
9221
10168
|
return;
|
|
9222
10169
|
}
|
|
9223
10170
|
const pages = await this.provider.loadAll();
|
|
9224
|
-
for (const [
|
|
10171
|
+
for (const [path5, content] of pages) {
|
|
9225
10172
|
const lineMap = buildLineMap(content);
|
|
9226
|
-
this.pageCache.set(
|
|
10173
|
+
this.pageCache.set(path5, content, lineMap);
|
|
10174
|
+
}
|
|
10175
|
+
for (const path5 of this.pageCache.keys()) {
|
|
10176
|
+
if (!pages.has(path5)) this.pageCache.delete(path5);
|
|
9227
10177
|
}
|
|
9228
10178
|
}
|
|
9229
10179
|
/**
|
|
@@ -9240,8 +10190,8 @@ var PageService = class {
|
|
|
9240
10190
|
* }
|
|
9241
10191
|
* ```
|
|
9242
10192
|
*/
|
|
9243
|
-
getPage(
|
|
9244
|
-
return this.pageCache.getContent(
|
|
10193
|
+
getPage(path5) {
|
|
10194
|
+
return this.pageCache.getContent(path5);
|
|
9245
10195
|
}
|
|
9246
10196
|
/**
|
|
9247
10197
|
* Get parsed page data
|
|
@@ -9260,8 +10210,8 @@ var PageService = class {
|
|
|
9260
10210
|
* }
|
|
9261
10211
|
* ```
|
|
9262
10212
|
*/
|
|
9263
|
-
getPageData(
|
|
9264
|
-
const content = this.pageCache.getContent(
|
|
10213
|
+
getPageData(path5) {
|
|
10214
|
+
const content = this.pageCache.getContent(path5);
|
|
9265
10215
|
if (!content) {
|
|
9266
10216
|
return null;
|
|
9267
10217
|
}
|
|
@@ -9291,15 +10241,15 @@ var PageService = class {
|
|
|
9291
10241
|
* });
|
|
9292
10242
|
* ```
|
|
9293
10243
|
*/
|
|
9294
|
-
async savePage(
|
|
10244
|
+
async savePage(path5, data) {
|
|
9295
10245
|
if (!this.provider) {
|
|
9296
10246
|
throw new Error("PageProvider not set");
|
|
9297
10247
|
}
|
|
9298
10248
|
const { _lineMap, ...dataToSave } = data;
|
|
9299
10249
|
const content = JSON.stringify(dataToSave, null, 2);
|
|
9300
|
-
await this.provider.save(
|
|
10250
|
+
await this.provider.save(path5, content);
|
|
9301
10251
|
const lineMap = buildLineMap(content);
|
|
9302
|
-
this.pageCache.set(
|
|
10252
|
+
this.pageCache.set(path5, content, lineMap);
|
|
9303
10253
|
}
|
|
9304
10254
|
/**
|
|
9305
10255
|
* Re-read a single page from disk via the provider and refresh the cache.
|
|
@@ -9313,22 +10263,22 @@ var PageService = class {
|
|
|
9313
10263
|
* partial mid-write or parse error), in which case the cache is left intact and
|
|
9314
10264
|
* the next watcher event retries.
|
|
9315
10265
|
*/
|
|
9316
|
-
async reloadPageFromDisk(
|
|
10266
|
+
async reloadPageFromDisk(path5) {
|
|
9317
10267
|
if (!this.provider) return false;
|
|
9318
10268
|
let content = null;
|
|
9319
10269
|
try {
|
|
9320
|
-
content = await this.provider.get(
|
|
10270
|
+
content = await this.provider.get(path5);
|
|
9321
10271
|
} catch (error) {
|
|
9322
|
-
logRuntimeError("pageService.reloadPageFromDisk", error, { path:
|
|
10272
|
+
logRuntimeError("pageService.reloadPageFromDisk", error, { path: path5 });
|
|
9323
10273
|
return false;
|
|
9324
10274
|
}
|
|
9325
10275
|
if (content) {
|
|
9326
|
-
this.pageCache.set(
|
|
10276
|
+
this.pageCache.set(path5, content, buildLineMap(content));
|
|
9327
10277
|
return true;
|
|
9328
10278
|
}
|
|
9329
10279
|
try {
|
|
9330
|
-
if (!await this.provider.exists(
|
|
9331
|
-
this.pageCache.delete(
|
|
10280
|
+
if (!await this.provider.exists(path5)) {
|
|
10281
|
+
this.pageCache.delete(path5);
|
|
9332
10282
|
return true;
|
|
9333
10283
|
}
|
|
9334
10284
|
} catch {
|
|
@@ -9351,10 +10301,10 @@ var PageService = class {
|
|
|
9351
10301
|
* await pageService.deletePage('/old-page', true);
|
|
9352
10302
|
* ```
|
|
9353
10303
|
*/
|
|
9354
|
-
async deletePage(
|
|
9355
|
-
this.pageCache.delete(
|
|
10304
|
+
async deletePage(path5, deleteFromStorage = false) {
|
|
10305
|
+
this.pageCache.delete(path5);
|
|
9356
10306
|
if (deleteFromStorage && this.provider) {
|
|
9357
|
-
await this.provider.delete(
|
|
10307
|
+
await this.provider.delete(path5);
|
|
9358
10308
|
}
|
|
9359
10309
|
}
|
|
9360
10310
|
/**
|
|
@@ -9382,26 +10332,34 @@ var PageService = class {
|
|
|
9382
10332
|
*
|
|
9383
10333
|
* @example
|
|
9384
10334
|
* ```typescript
|
|
9385
|
-
* const pages = pageService.getAllPagesWithInfo();
|
|
10335
|
+
* const pages = await pageService.getAllPagesWithInfo();
|
|
9386
10336
|
* // [{ path: "/", isDraft: false }, { path: "/about", isDraft: true }]
|
|
9387
10337
|
* ```
|
|
9388
10338
|
*/
|
|
9389
|
-
getAllPagesWithInfo() {
|
|
10339
|
+
async getAllPagesWithInfo() {
|
|
9390
10340
|
const paths = this.pageCache.keys().filter((p) => !p.startsWith("/_sketch/"));
|
|
9391
|
-
return
|
|
9392
|
-
|
|
9393
|
-
|
|
9394
|
-
|
|
9395
|
-
|
|
9396
|
-
|
|
9397
|
-
|
|
9398
|
-
|
|
9399
|
-
|
|
9400
|
-
|
|
9401
|
-
|
|
9402
|
-
|
|
9403
|
-
|
|
9404
|
-
|
|
10341
|
+
return Promise.all(
|
|
10342
|
+
paths.map(async (path5) => {
|
|
10343
|
+
const pageData = this.getPageData(path5);
|
|
10344
|
+
const pathWithoutSlash = path5 === "/" ? "index" : path5.substring(1);
|
|
10345
|
+
const slashIndex = pathWithoutSlash.lastIndexOf("/");
|
|
10346
|
+
const folder = slashIndex > 0 ? pathWithoutSlash.substring(0, slashIndex) : void 0;
|
|
10347
|
+
const metaSource = pageData?.meta?.source;
|
|
10348
|
+
const source = metaSource === "cms" || metaSource === "ssr" ? metaSource : void 0;
|
|
10349
|
+
let sourcePath;
|
|
10350
|
+
if (source === "cms" && this.provider?.resolveSourcePath) {
|
|
10351
|
+
const abs = await this.provider.resolveSourcePath(path5);
|
|
10352
|
+
if (abs) sourcePath = relative(getProjectRoot(), abs).split(sep).join("/");
|
|
10353
|
+
}
|
|
10354
|
+
return {
|
|
10355
|
+
path: path5,
|
|
10356
|
+
isDraft: pageData?.meta?.draft === true,
|
|
10357
|
+
...folder ? { folder } : {},
|
|
10358
|
+
...source ? { source } : {},
|
|
10359
|
+
...sourcePath ? { sourcePath } : {}
|
|
10360
|
+
};
|
|
10361
|
+
})
|
|
10362
|
+
);
|
|
9405
10363
|
}
|
|
9406
10364
|
/**
|
|
9407
10365
|
* Get line map for a page
|
|
@@ -9411,8 +10369,8 @@ var PageService = class {
|
|
|
9411
10369
|
* @param path - Page path (e.g., "/" or "/about")
|
|
9412
10370
|
* @returns LineMap object or undefined if page not found
|
|
9413
10371
|
*/
|
|
9414
|
-
getLineMap(
|
|
9415
|
-
return this.pageCache.getLineMap(
|
|
10372
|
+
getLineMap(path5) {
|
|
10373
|
+
return this.pageCache.getLineMap(path5);
|
|
9416
10374
|
}
|
|
9417
10375
|
/**
|
|
9418
10376
|
* Pages base directory — provider-aware (e.g. `src/pages` for astro projects),
|
|
@@ -9436,12 +10394,12 @@ var PageService = class {
|
|
|
9436
10394
|
*/
|
|
9437
10395
|
getAllFolders() {
|
|
9438
10396
|
const pagesDir = this.pagesBaseDir();
|
|
9439
|
-
if (!
|
|
10397
|
+
if (!existsSync6(pagesDir)) {
|
|
9440
10398
|
return [];
|
|
9441
10399
|
}
|
|
9442
10400
|
const collectFolders = (dir, prefix) => {
|
|
9443
10401
|
try {
|
|
9444
|
-
const entries =
|
|
10402
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
9445
10403
|
const result = [];
|
|
9446
10404
|
for (const entry of entries) {
|
|
9447
10405
|
if (!entry.isDirectory()) continue;
|
|
@@ -9479,7 +10437,7 @@ var PageService = class {
|
|
|
9479
10437
|
}
|
|
9480
10438
|
const pagesDir = this.pagesBaseDir();
|
|
9481
10439
|
const folderPath = join4(pagesDir, trimmed);
|
|
9482
|
-
if (
|
|
10440
|
+
if (existsSync6(folderPath)) {
|
|
9483
10441
|
throw new Error("Folder already exists");
|
|
9484
10442
|
}
|
|
9485
10443
|
mkdirSync(folderPath, { recursive: true });
|
|
@@ -9507,11 +10465,11 @@ var PageService = class {
|
|
|
9507
10465
|
}
|
|
9508
10466
|
if (newFolder) {
|
|
9509
10467
|
const targetDir = join4(pagesDir, newFolder);
|
|
9510
|
-
if (!
|
|
10468
|
+
if (!existsSync6(targetDir)) {
|
|
9511
10469
|
mkdirSync(targetDir, { recursive: true });
|
|
9512
10470
|
}
|
|
9513
10471
|
}
|
|
9514
|
-
if (!
|
|
10472
|
+
if (!existsSync6(sourceFile)) {
|
|
9515
10473
|
throw new Error(`Page not found: ${pagePath}`);
|
|
9516
10474
|
}
|
|
9517
10475
|
await rename2(sourceFile, targetFile);
|
|
@@ -9526,7 +10484,7 @@ var PageService = class {
|
|
|
9526
10484
|
const sourceDir = slashIndex >= 0 ? join4(pagesDir, pageName.substring(0, slashIndex)) : null;
|
|
9527
10485
|
if (sourceDir && sourceDir !== pagesDir) {
|
|
9528
10486
|
try {
|
|
9529
|
-
const remaining =
|
|
10487
|
+
const remaining = readdirSync3(sourceDir);
|
|
9530
10488
|
if (remaining.length === 0) {
|
|
9531
10489
|
rmdirSync(sourceDir);
|
|
9532
10490
|
}
|
|
@@ -9574,10 +10532,10 @@ var PageService = class {
|
|
|
9574
10532
|
const ext = this.pageExt();
|
|
9575
10533
|
const sourceFile = join4(pagesDir, `${oldName}${ext}`);
|
|
9576
10534
|
const targetFile = join4(pagesDir, `${newName}${ext}`);
|
|
9577
|
-
if (!
|
|
10535
|
+
if (!existsSync6(sourceFile)) {
|
|
9578
10536
|
throw new Error(`Page not found: ${oldPath}`);
|
|
9579
10537
|
}
|
|
9580
|
-
if (
|
|
10538
|
+
if (existsSync6(targetFile)) {
|
|
9581
10539
|
throw new Error(`Page already exists: ${newPath}`);
|
|
9582
10540
|
}
|
|
9583
10541
|
await rename2(sourceFile, targetFile);
|
|
@@ -9605,17 +10563,17 @@ var PageService = class {
|
|
|
9605
10563
|
getSlugMappings() {
|
|
9606
10564
|
const mappings = [];
|
|
9607
10565
|
const paths = this.pageCache.keys();
|
|
9608
|
-
for (const
|
|
9609
|
-
const pageData = this.getPageData(
|
|
10566
|
+
for (const path5 of paths) {
|
|
10567
|
+
const pageData = this.getPageData(path5);
|
|
9610
10568
|
if (pageData?.meta?.slugs) {
|
|
9611
|
-
const pageId =
|
|
10569
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
9612
10570
|
mappings.push({
|
|
9613
10571
|
pageId,
|
|
9614
10572
|
slugs: pageData.meta.slugs
|
|
9615
10573
|
});
|
|
9616
10574
|
} else {
|
|
9617
|
-
const pageId =
|
|
9618
|
-
const defaultSlug =
|
|
10575
|
+
const pageId = path5 === "/" ? "index" : path5.substring(1);
|
|
10576
|
+
const defaultSlug = path5 === "/" ? "" : path5.substring(1);
|
|
9619
10577
|
mappings.push({
|
|
9620
10578
|
pageId,
|
|
9621
10579
|
slugs: { _default: defaultSlug }
|
|
@@ -9628,7 +10586,7 @@ var PageService = class {
|
|
|
9628
10586
|
|
|
9629
10587
|
// lib/server/services/componentService.ts
|
|
9630
10588
|
import { join as join5 } from "node:path";
|
|
9631
|
-
import { existsSync as
|
|
10589
|
+
import { existsSync as existsSync7, mkdirSync as mkdirSync2, readdirSync as readdirSync4, rmdirSync as rmdirSync2 } from "node:fs";
|
|
9632
10590
|
var ComponentService = class {
|
|
9633
10591
|
components = /* @__PURE__ */ new Map();
|
|
9634
10592
|
componentCategories = /* @__PURE__ */ new Map();
|
|
@@ -9637,6 +10595,36 @@ var ComponentService = class {
|
|
|
9637
10595
|
writer;
|
|
9638
10596
|
loadErrors = /* @__PURE__ */ new Map();
|
|
9639
10597
|
loadWarnings = [];
|
|
10598
|
+
/** Per-component serialization of whole-file re-emits. @see withComponentWriteLock */
|
|
10599
|
+
writeLocks = /* @__PURE__ */ new Map();
|
|
10600
|
+
/**
|
|
10601
|
+
* Serialize whole-file re-emits for one component.
|
|
10602
|
+
*
|
|
10603
|
+
* `saveComponentCSS` and `saveComponentJavaScript` each read the in-memory
|
|
10604
|
+
* definition, re-emit the ENTIRE `.astro` file, then write the result back.
|
|
10605
|
+
* Fired concurrently — the Custom Code modal's Save issues a JS save AND a CSS
|
|
10606
|
+
* save together — their read-modify-write races: each reads a snapshot taken
|
|
10607
|
+
* before the other's `components.set`, so the writer that lands last clobbers
|
|
10608
|
+
* the other field with a stale value (a fresh CSS edit silently reverting is
|
|
10609
|
+
* the visible symptom). Chaining per component name makes each op observe the
|
|
10610
|
+
* previous op's committed state. Prior failures are swallowed on the chain tail
|
|
10611
|
+
* so one bad write can't wedge the queue.
|
|
10612
|
+
*/
|
|
10613
|
+
withComponentWriteLock(name, fn) {
|
|
10614
|
+
const prev = this.writeLocks.get(name) ?? Promise.resolve();
|
|
10615
|
+
const run = prev.then(fn, fn);
|
|
10616
|
+
const tail = run.then(
|
|
10617
|
+
() => {
|
|
10618
|
+
},
|
|
10619
|
+
() => {
|
|
10620
|
+
}
|
|
10621
|
+
);
|
|
10622
|
+
this.writeLocks.set(name, tail);
|
|
10623
|
+
tail.finally(() => {
|
|
10624
|
+
if (this.writeLocks.get(name) === tail) this.writeLocks.delete(name);
|
|
10625
|
+
});
|
|
10626
|
+
return run;
|
|
10627
|
+
}
|
|
9640
10628
|
/**
|
|
9641
10629
|
* Creates a new ComponentService instance
|
|
9642
10630
|
*
|
|
@@ -9891,12 +10879,12 @@ var ComponentService = class {
|
|
|
9891
10879
|
*/
|
|
9892
10880
|
findComponentCategoryOnDisk(name) {
|
|
9893
10881
|
const componentsDir = this.componentsBaseDir();
|
|
9894
|
-
if (!
|
|
10882
|
+
if (!existsSync7(componentsDir)) return void 0;
|
|
9895
10883
|
const ext = this.writer ? null : ".json";
|
|
9896
10884
|
const existsAt = (dir) => {
|
|
9897
|
-
if (ext) return
|
|
10885
|
+
if (ext) return existsSync7(join5(dir, `${name}${ext}`));
|
|
9898
10886
|
try {
|
|
9899
|
-
return
|
|
10887
|
+
return readdirSync4(dir, { withFileTypes: true }).some(
|
|
9900
10888
|
(e) => e.isFile() && (e.name === `${name}.json` || e.name === `${name}.astro`)
|
|
9901
10889
|
);
|
|
9902
10890
|
} catch {
|
|
@@ -9905,7 +10893,7 @@ var ComponentService = class {
|
|
|
9905
10893
|
};
|
|
9906
10894
|
if (existsAt(componentsDir)) return void 0;
|
|
9907
10895
|
try {
|
|
9908
|
-
for (const entry of
|
|
10896
|
+
for (const entry of readdirSync4(componentsDir, { withFileTypes: true })) {
|
|
9909
10897
|
if (entry.isDirectory() && existsAt(join5(componentsDir, entry.name))) {
|
|
9910
10898
|
return entry.name;
|
|
9911
10899
|
}
|
|
@@ -9986,7 +10974,7 @@ var ComponentService = class {
|
|
|
9986
10974
|
await this.writer.writeComponent(componentDir, name, dataWithJS);
|
|
9987
10975
|
} else {
|
|
9988
10976
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
9989
|
-
if (targetCategory && !
|
|
10977
|
+
if (targetCategory && !existsSync7(componentDir)) {
|
|
9990
10978
|
mkdirSync2(componentDir, { recursive: true });
|
|
9991
10979
|
}
|
|
9992
10980
|
const filePath = join5(componentDir, `${name}.json`);
|
|
@@ -10015,10 +11003,12 @@ var ComponentService = class {
|
|
|
10015
11003
|
async saveComponentJavaScript(name, javascript) {
|
|
10016
11004
|
const componentDir = this.getComponentDir(name);
|
|
10017
11005
|
if (this.writer) {
|
|
10018
|
-
|
|
10019
|
-
|
|
10020
|
-
|
|
10021
|
-
|
|
11006
|
+
await this.withComponentWriteLock(name, async () => {
|
|
11007
|
+
const current = this.components.get(name);
|
|
11008
|
+
await this.writer.writeJavaScript(componentDir, name, javascript || "", current);
|
|
11009
|
+
const next = current ? { ...current, component: { ...current.component, javascript: javascript || "" } } : { component: { javascript: javascript || "" } };
|
|
11010
|
+
this.components.set(name, next);
|
|
11011
|
+
});
|
|
10022
11012
|
return;
|
|
10023
11013
|
}
|
|
10024
11014
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10061,10 +11051,12 @@ var ComponentService = class {
|
|
|
10061
11051
|
async saveComponentCSS(name, css) {
|
|
10062
11052
|
const componentDir = this.getComponentDir(name);
|
|
10063
11053
|
if (this.writer) {
|
|
10064
|
-
|
|
10065
|
-
|
|
10066
|
-
|
|
10067
|
-
|
|
11054
|
+
await this.withComponentWriteLock(name, async () => {
|
|
11055
|
+
const current = this.components.get(name);
|
|
11056
|
+
await this.writer.writeCSS(componentDir, name, css || "", current);
|
|
11057
|
+
const next = current ? { ...current, component: { ...current.component, css: css || "" } } : { component: { css: css || "" } };
|
|
11058
|
+
this.components.set(name, next);
|
|
11059
|
+
});
|
|
10068
11060
|
return;
|
|
10069
11061
|
}
|
|
10070
11062
|
const writeFile3 = this.fs ? this.fs.writeFile.bind(this.fs) : (await import("node:fs/promises")).writeFile;
|
|
@@ -10103,11 +11095,11 @@ var ComponentService = class {
|
|
|
10103
11095
|
*/
|
|
10104
11096
|
getAllFolders() {
|
|
10105
11097
|
const componentsDir = this.componentsBaseDir();
|
|
10106
|
-
if (!
|
|
11098
|
+
if (!existsSync7(componentsDir)) {
|
|
10107
11099
|
return [];
|
|
10108
11100
|
}
|
|
10109
11101
|
try {
|
|
10110
|
-
const entries =
|
|
11102
|
+
const entries = readdirSync4(componentsDir, { withFileTypes: true });
|
|
10111
11103
|
return entries.filter((entry) => entry.isDirectory()).map((entry) => entry.name).sort();
|
|
10112
11104
|
} catch {
|
|
10113
11105
|
return [];
|
|
@@ -10139,7 +11131,7 @@ var ComponentService = class {
|
|
|
10139
11131
|
}
|
|
10140
11132
|
const componentsDir = this.componentsBaseDir();
|
|
10141
11133
|
const folderPath = join5(componentsDir, folderName);
|
|
10142
|
-
if (
|
|
11134
|
+
if (existsSync7(folderPath)) {
|
|
10143
11135
|
throw new Error("Folder already exists");
|
|
10144
11136
|
}
|
|
10145
11137
|
mkdirSync2(folderPath, { recursive: true });
|
|
@@ -10177,7 +11169,7 @@ var ComponentService = class {
|
|
|
10177
11169
|
if (this.writer) {
|
|
10178
11170
|
await this.writer.moveComponent(sourceDir, targetDir, name);
|
|
10179
11171
|
} else {
|
|
10180
|
-
if (targetCategory && !
|
|
11172
|
+
if (targetCategory && !existsSync7(targetDir)) {
|
|
10181
11173
|
mkdirSync2(targetDir, { recursive: true });
|
|
10182
11174
|
}
|
|
10183
11175
|
const extensions = [".json", ".js", ".css"];
|
|
@@ -10185,7 +11177,7 @@ var ComponentService = class {
|
|
|
10185
11177
|
for (const ext of extensions) {
|
|
10186
11178
|
const sourcePath = join5(sourceDir, `${name}${ext}`);
|
|
10187
11179
|
const targetPath = join5(targetDir, `${name}${ext}`);
|
|
10188
|
-
if (
|
|
11180
|
+
if (existsSync7(sourcePath)) {
|
|
10189
11181
|
await rename2(sourcePath, targetPath);
|
|
10190
11182
|
}
|
|
10191
11183
|
}
|
|
@@ -10193,7 +11185,7 @@ var ComponentService = class {
|
|
|
10193
11185
|
this.componentCategories.set(name, targetCategory);
|
|
10194
11186
|
if (currentCategory) {
|
|
10195
11187
|
try {
|
|
10196
|
-
const remaining =
|
|
11188
|
+
const remaining = readdirSync4(sourceDir);
|
|
10197
11189
|
if (remaining.length === 0) {
|
|
10198
11190
|
rmdirSync2(sourceDir);
|
|
10199
11191
|
}
|
|
@@ -10248,7 +11240,7 @@ var ComponentService = class {
|
|
|
10248
11240
|
await this.writer.moveComponent(dir, dir, oldName, trimmedNew);
|
|
10249
11241
|
} else {
|
|
10250
11242
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10251
|
-
if (
|
|
11243
|
+
if (existsSync7(join5(dir, `${trimmedNew}${ext}`))) {
|
|
10252
11244
|
throw new Error(`File "${trimmedNew}${ext}" already exists in components/${category ?? ""}`);
|
|
10253
11245
|
}
|
|
10254
11246
|
}
|
|
@@ -10256,7 +11248,7 @@ var ComponentService = class {
|
|
|
10256
11248
|
for (const ext of [".json", ".js", ".css"]) {
|
|
10257
11249
|
const src = join5(dir, `${oldName}${ext}`);
|
|
10258
11250
|
const dst = join5(dir, `${trimmedNew}${ext}`);
|
|
10259
|
-
if (
|
|
11251
|
+
if (existsSync7(src)) {
|
|
10260
11252
|
await rename2(src, dst);
|
|
10261
11253
|
}
|
|
10262
11254
|
}
|
|
@@ -10290,11 +11282,11 @@ var ComponentService = class {
|
|
|
10290
11282
|
let pageRefs = 0;
|
|
10291
11283
|
if (pageService) {
|
|
10292
11284
|
const pagePaths = pageService.getAllPagePaths();
|
|
10293
|
-
for (const
|
|
10294
|
-
const pageData = pageService.getPageData(
|
|
11285
|
+
for (const path5 of pagePaths) {
|
|
11286
|
+
const pageData = pageService.getPageData(path5);
|
|
10295
11287
|
if (!pageData || !("root" in pageData) || !pageData.root) continue;
|
|
10296
11288
|
if (rewriteComponentRefs(pageData.root, oldName, trimmedNew)) {
|
|
10297
|
-
await pageService.savePage(
|
|
11289
|
+
await pageService.savePage(path5, pageData);
|
|
10298
11290
|
pageRefs++;
|
|
10299
11291
|
}
|
|
10300
11292
|
}
|
|
@@ -10452,12 +11444,12 @@ var CMSService = class {
|
|
|
10452
11444
|
* @param locale - Optional locale for i18n slug matching
|
|
10453
11445
|
* @returns CMSRouteMatch if matched, null otherwise
|
|
10454
11446
|
*/
|
|
10455
|
-
async matchRoute(
|
|
11447
|
+
async matchRoute(path5, locale) {
|
|
10456
11448
|
if (!this.provider) {
|
|
10457
11449
|
return null;
|
|
10458
11450
|
}
|
|
10459
11451
|
for (const route of this.routePatterns) {
|
|
10460
|
-
const match =
|
|
11452
|
+
const match = path5.match(route.regex);
|
|
10461
11453
|
if (match) {
|
|
10462
11454
|
const urlSlug = match[route.slugGroup];
|
|
10463
11455
|
if (urlSlug === void 0) continue;
|
|
@@ -10566,11 +11558,11 @@ var CMSService = class {
|
|
|
10566
11558
|
if (options?.excludeDrafts && isItemDraftForLocale(item, locale)) continue;
|
|
10567
11559
|
const localizedSlug = slugValue[locale];
|
|
10568
11560
|
if (localizedSlug) {
|
|
10569
|
-
const
|
|
11561
|
+
const path5 = urlPattern.replace("{{slug}}", localizedSlug);
|
|
10570
11562
|
if (locale === defaultLocale) {
|
|
10571
|
-
urls.push(
|
|
11563
|
+
urls.push(path5);
|
|
10572
11564
|
} else {
|
|
10573
|
-
urls.push(`/${locale}${
|
|
11565
|
+
urls.push(`/${locale}${path5}`);
|
|
10574
11566
|
}
|
|
10575
11567
|
}
|
|
10576
11568
|
}
|
|
@@ -10822,7 +11814,7 @@ var CMSService = class {
|
|
|
10822
11814
|
};
|
|
10823
11815
|
|
|
10824
11816
|
// lib/server/providers/fileSystemCMSProvider.ts
|
|
10825
|
-
import { existsSync as
|
|
11817
|
+
import { existsSync as existsSync8, readdirSync as readdirSync5, mkdirSync as mkdirSync3 } from "node:fs";
|
|
10826
11818
|
import { join as join6 } from "node:path";
|
|
10827
11819
|
var log6 = createLogger("FileSystemCMSProvider");
|
|
10828
11820
|
var DRAFT_FILE_SUFFIX = `${CMS_DRAFT_SUFFIX}.json`;
|
|
@@ -10895,10 +11887,10 @@ var FileSystemCMSProvider = class {
|
|
|
10895
11887
|
return this.schemaCache;
|
|
10896
11888
|
}
|
|
10897
11889
|
const schemas = /* @__PURE__ */ new Map();
|
|
10898
|
-
if (!
|
|
11890
|
+
if (!existsSync8(this.templatesDir)) {
|
|
10899
11891
|
return schemas;
|
|
10900
11892
|
}
|
|
10901
|
-
const files =
|
|
11893
|
+
const files = readdirSync5(this.templatesDir);
|
|
10902
11894
|
const jsonFiles = files.filter((f) => f.endsWith(".json"));
|
|
10903
11895
|
const results = await Promise.all(
|
|
10904
11896
|
jsonFiles.map(async (file) => {
|
|
@@ -10928,10 +11920,10 @@ var FileSystemCMSProvider = class {
|
|
|
10928
11920
|
async getItems(collection) {
|
|
10929
11921
|
this.validateCollection(collection);
|
|
10930
11922
|
const collectionDir = join6(this.cmsDir, collection);
|
|
10931
|
-
if (!
|
|
11923
|
+
if (!existsSync8(collectionDir)) {
|
|
10932
11924
|
return [];
|
|
10933
11925
|
}
|
|
10934
|
-
const files =
|
|
11926
|
+
const files = readdirSync5(collectionDir);
|
|
10935
11927
|
const jsonFiles = files.filter((f) => f.endsWith(".json") && !f.endsWith(DRAFT_FILE_SUFFIX));
|
|
10936
11928
|
const results = await Promise.all(
|
|
10937
11929
|
jsonFiles.map(async (file) => {
|
|
@@ -11021,7 +12013,7 @@ var FileSystemCMSProvider = class {
|
|
|
11021
12013
|
}
|
|
11022
12014
|
this.validateFilename(filename);
|
|
11023
12015
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11024
|
-
if (!
|
|
12016
|
+
if (!existsSync8(collectionDir)) {
|
|
11025
12017
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11026
12018
|
}
|
|
11027
12019
|
const itemData = stripTransient(item);
|
|
@@ -11036,11 +12028,11 @@ var FileSystemCMSProvider = class {
|
|
|
11036
12028
|
this.validateFilename(filename);
|
|
11037
12029
|
const { unlink } = await import("node:fs/promises");
|
|
11038
12030
|
const publishedPath = join6(this.cmsDir, collection, `${filename}.json`);
|
|
11039
|
-
if (
|
|
12031
|
+
if (existsSync8(publishedPath)) {
|
|
11040
12032
|
await unlink(publishedPath);
|
|
11041
12033
|
}
|
|
11042
12034
|
const draftPath = this.draftPath(collection, filename);
|
|
11043
|
-
if (
|
|
12035
|
+
if (existsSync8(draftPath)) {
|
|
11044
12036
|
await unlink(draftPath);
|
|
11045
12037
|
}
|
|
11046
12038
|
}
|
|
@@ -11074,8 +12066,8 @@ var FileSystemCMSProvider = class {
|
|
|
11074
12066
|
async getAllDrafts(collection) {
|
|
11075
12067
|
this.validateCollection(collection);
|
|
11076
12068
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11077
|
-
if (!
|
|
11078
|
-
const files =
|
|
12069
|
+
if (!existsSync8(collectionDir)) return [];
|
|
12070
|
+
const files = readdirSync5(collectionDir).filter((f) => f.endsWith(DRAFT_FILE_SUFFIX));
|
|
11079
12071
|
const results = await Promise.all(
|
|
11080
12072
|
files.map(async (file) => {
|
|
11081
12073
|
const filePath = join6(collectionDir, file);
|
|
@@ -11100,7 +12092,7 @@ var FileSystemCMSProvider = class {
|
|
|
11100
12092
|
async hasDraft(collection, filename) {
|
|
11101
12093
|
this.validateCollection(collection);
|
|
11102
12094
|
this.validateFilename(filename);
|
|
11103
|
-
return
|
|
12095
|
+
return existsSync8(this.draftPath(collection, filename));
|
|
11104
12096
|
}
|
|
11105
12097
|
/**
|
|
11106
12098
|
* Save the draft version of an item. Loose validation — drafts may have
|
|
@@ -11135,7 +12127,7 @@ var FileSystemCMSProvider = class {
|
|
|
11135
12127
|
}
|
|
11136
12128
|
this.validateFilename(filename);
|
|
11137
12129
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11138
|
-
if (!
|
|
12130
|
+
if (!existsSync8(collectionDir)) {
|
|
11139
12131
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11140
12132
|
}
|
|
11141
12133
|
const itemData = stripTransient(item);
|
|
@@ -11155,7 +12147,7 @@ var FileSystemCMSProvider = class {
|
|
|
11155
12147
|
this.validateFilename(filename);
|
|
11156
12148
|
const { unlink } = await import("node:fs/promises");
|
|
11157
12149
|
const filePath = this.draftPath(collection, filename);
|
|
11158
|
-
if (
|
|
12150
|
+
if (existsSync8(filePath)) {
|
|
11159
12151
|
await unlink(filePath);
|
|
11160
12152
|
}
|
|
11161
12153
|
}
|
|
@@ -11184,13 +12176,13 @@ var FileSystemCMSProvider = class {
|
|
|
11184
12176
|
throw new Error(`Cannot publish invalid draft: ${messages}`);
|
|
11185
12177
|
}
|
|
11186
12178
|
const collectionDir = join6(this.cmsDir, collection);
|
|
11187
|
-
if (!
|
|
12179
|
+
if (!existsSync8(collectionDir)) {
|
|
11188
12180
|
mkdirSync3(collectionDir, { recursive: true });
|
|
11189
12181
|
}
|
|
11190
12182
|
const itemData = stripTransient(validation.data);
|
|
11191
12183
|
const publishedPath = join6(collectionDir, `${filename}.json`);
|
|
11192
12184
|
await writeFile3(publishedPath, JSON.stringify(itemData, null, 2), "utf-8");
|
|
11193
|
-
if (
|
|
12185
|
+
if (existsSync8(draftFilePath)) {
|
|
11194
12186
|
await unlink(draftFilePath);
|
|
11195
12187
|
}
|
|
11196
12188
|
return normalizeItem(itemData, filename);
|
|
@@ -11211,18 +12203,18 @@ var FileSystemCMSProvider = class {
|
|
|
11211
12203
|
);
|
|
11212
12204
|
}
|
|
11213
12205
|
const { writeFile: writeFile3, mkdir } = await import("node:fs/promises");
|
|
11214
|
-
if (!
|
|
12206
|
+
if (!existsSync8(this.templatesDir)) {
|
|
11215
12207
|
await mkdir(this.templatesDir, { recursive: true });
|
|
11216
12208
|
}
|
|
11217
12209
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11218
|
-
if (
|
|
12210
|
+
if (existsSync8(pageFilePath)) {
|
|
11219
12211
|
throw new Error(`Page file already exists: templates/${collectionId}.json`);
|
|
11220
12212
|
}
|
|
11221
12213
|
await writeFile3(pageFilePath, JSON.stringify(pageData, null, 2), "utf-8");
|
|
11222
12214
|
const schemaSource = pageData?.meta?.cms?.source;
|
|
11223
12215
|
if (schemaSource !== "sanity") {
|
|
11224
12216
|
const collectionDir = join6(this.cmsDir, collectionId);
|
|
11225
|
-
if (!
|
|
12217
|
+
if (!existsSync8(collectionDir)) {
|
|
11226
12218
|
await mkdir(collectionDir, { recursive: true });
|
|
11227
12219
|
}
|
|
11228
12220
|
}
|
|
@@ -11238,12 +12230,12 @@ var FileSystemCMSProvider = class {
|
|
|
11238
12230
|
`Invalid collection ID: "${collectionId}". Collection IDs must contain only letters, numbers, hyphens, and underscores.`
|
|
11239
12231
|
);
|
|
11240
12232
|
}
|
|
11241
|
-
const { readFile:
|
|
12233
|
+
const { readFile: readFile3, writeFile: writeFile3 } = await import("node:fs/promises");
|
|
11242
12234
|
const pageFilePath = join6(this.templatesDir, `${collectionId}.json`);
|
|
11243
|
-
if (!
|
|
12235
|
+
if (!existsSync8(pageFilePath)) {
|
|
11244
12236
|
throw new Error(`Collection not found: ${collectionId}`);
|
|
11245
12237
|
}
|
|
11246
|
-
const content = await
|
|
12238
|
+
const content = await readFile3(pageFilePath, "utf-8");
|
|
11247
12239
|
const pageData = JSON.parse(content);
|
|
11248
12240
|
pageData.meta.cms = {
|
|
11249
12241
|
...pageData.meta.cms,
|
|
@@ -11262,26 +12254,26 @@ var PageCache = class {
|
|
|
11262
12254
|
/**
|
|
11263
12255
|
* Store page content and line map in cache
|
|
11264
12256
|
*/
|
|
11265
|
-
set(
|
|
11266
|
-
this.cache.set(
|
|
12257
|
+
set(path5, content, lineMap) {
|
|
12258
|
+
this.cache.set(path5, { content, lineMap });
|
|
11267
12259
|
}
|
|
11268
12260
|
/**
|
|
11269
12261
|
* Get cached page data
|
|
11270
12262
|
*/
|
|
11271
|
-
get(
|
|
11272
|
-
return this.cache.get(
|
|
12263
|
+
get(path5) {
|
|
12264
|
+
return this.cache.get(path5);
|
|
11273
12265
|
}
|
|
11274
12266
|
/**
|
|
11275
12267
|
* Get page content from cache
|
|
11276
12268
|
*/
|
|
11277
|
-
getContent(
|
|
11278
|
-
return this.cache.get(
|
|
12269
|
+
getContent(path5) {
|
|
12270
|
+
return this.cache.get(path5)?.content;
|
|
11279
12271
|
}
|
|
11280
12272
|
/**
|
|
11281
12273
|
* Get line map from cache
|
|
11282
12274
|
*/
|
|
11283
|
-
getLineMap(
|
|
11284
|
-
return this.cache.get(
|
|
12275
|
+
getLineMap(path5) {
|
|
12276
|
+
return this.cache.get(path5)?.lineMap;
|
|
11285
12277
|
}
|
|
11286
12278
|
/**
|
|
11287
12279
|
* Get line range for a specific element path
|
|
@@ -11292,14 +12284,14 @@ var PageCache = class {
|
|
|
11292
12284
|
/**
|
|
11293
12285
|
* Delete page from cache
|
|
11294
12286
|
*/
|
|
11295
|
-
delete(
|
|
11296
|
-
this.cache.delete(
|
|
12287
|
+
delete(path5) {
|
|
12288
|
+
this.cache.delete(path5);
|
|
11297
12289
|
}
|
|
11298
12290
|
/**
|
|
11299
12291
|
* Check if page exists in cache
|
|
11300
12292
|
*/
|
|
11301
|
-
has(
|
|
11302
|
-
return this.cache.has(
|
|
12293
|
+
has(path5) {
|
|
12294
|
+
return this.cache.has(path5);
|
|
11303
12295
|
}
|
|
11304
12296
|
/**
|
|
11305
12297
|
* Get all page paths
|
|
@@ -11338,21 +12330,21 @@ var DraftPageStore = class {
|
|
|
11338
12330
|
* Store a draft for a page path. The value is the raw JSON string the
|
|
11339
12331
|
* SSR pipeline expects, matching PageService.getPage()'s contract.
|
|
11340
12332
|
*/
|
|
11341
|
-
set(
|
|
11342
|
-
this.drafts.set(
|
|
12333
|
+
set(path5, content) {
|
|
12334
|
+
this.drafts.set(path5, content);
|
|
11343
12335
|
}
|
|
11344
12336
|
/**
|
|
11345
12337
|
* Get the current draft string for a page path, if any.
|
|
11346
12338
|
*/
|
|
11347
|
-
get(
|
|
11348
|
-
return this.drafts.get(
|
|
12339
|
+
get(path5) {
|
|
12340
|
+
return this.drafts.get(path5);
|
|
11349
12341
|
}
|
|
11350
12342
|
/**
|
|
11351
12343
|
* Drop the draft for a specific path. Called when the page is saved to
|
|
11352
12344
|
* disk so subsequent renders read the persisted version.
|
|
11353
12345
|
*/
|
|
11354
|
-
clear(
|
|
11355
|
-
this.drafts.delete(
|
|
12346
|
+
clear(path5) {
|
|
12347
|
+
this.drafts.delete(path5);
|
|
11356
12348
|
}
|
|
11357
12349
|
/**
|
|
11358
12350
|
* Drop every draft. Used on shutdown / project switch.
|
|
@@ -11360,14 +12352,19 @@ var DraftPageStore = class {
|
|
|
11360
12352
|
clearAll() {
|
|
11361
12353
|
this.drafts.clear();
|
|
11362
12354
|
}
|
|
11363
|
-
has(
|
|
11364
|
-
return this.drafts.has(
|
|
12355
|
+
has(path5) {
|
|
12356
|
+
return this.drafts.has(path5);
|
|
11365
12357
|
}
|
|
11366
12358
|
};
|
|
11367
12359
|
|
|
11368
12360
|
// lib/server/fileWatcher.ts
|
|
11369
|
-
import { watch, existsSync as
|
|
11370
|
-
import { basename as basename2, dirname as dirname2, join as join7, relative } from "node:path";
|
|
12361
|
+
import { watch, existsSync as existsSync9, statSync as statSync2, readdirSync as readdirSync6 } from "node:fs";
|
|
12362
|
+
import { basename as basename2, dirname as dirname2, join as join7, relative as relative2 } from "node:path";
|
|
12363
|
+
|
|
12364
|
+
// lib/shared/utils/fileUtils.ts
|
|
12365
|
+
var collapseFolderIndex = (name) => name.endsWith("/index") ? name.slice(0, -"/index".length) : name;
|
|
12366
|
+
|
|
12367
|
+
// lib/server/fileWatcher.ts
|
|
11371
12368
|
function watchRecursive(root, listener) {
|
|
11372
12369
|
const nested = [];
|
|
11373
12370
|
const watchedDirs = /* @__PURE__ */ new Set();
|
|
@@ -11375,17 +12372,17 @@ function watchRecursive(root, listener) {
|
|
|
11375
12372
|
const coverNewDir = (abs) => {
|
|
11376
12373
|
let isDir = false;
|
|
11377
12374
|
try {
|
|
11378
|
-
isDir =
|
|
12375
|
+
isDir = statSync2(abs).isDirectory();
|
|
11379
12376
|
} catch {
|
|
11380
12377
|
return;
|
|
11381
12378
|
}
|
|
11382
12379
|
if (!isDir || watchedDirs.has(abs)) return;
|
|
11383
12380
|
attachDir(abs);
|
|
11384
12381
|
try {
|
|
11385
|
-
for (const e of
|
|
12382
|
+
for (const e of readdirSync6(abs, { withFileTypes: true })) {
|
|
11386
12383
|
const child = join7(abs, e.name);
|
|
11387
12384
|
if (e.isDirectory()) coverNewDir(child);
|
|
11388
|
-
else listener("rename",
|
|
12385
|
+
else listener("rename", relative2(root, child));
|
|
11389
12386
|
}
|
|
11390
12387
|
} catch {
|
|
11391
12388
|
}
|
|
@@ -11398,7 +12395,7 @@ function watchRecursive(root, listener) {
|
|
|
11398
12395
|
w = watch(absDir, { recursive: true }, (event, filename) => {
|
|
11399
12396
|
if (!filename) return;
|
|
11400
12397
|
const abs = join7(absDir, filename);
|
|
11401
|
-
listener(event,
|
|
12398
|
+
listener(event, relative2(root, abs));
|
|
11402
12399
|
coverNewDir(abs);
|
|
11403
12400
|
});
|
|
11404
12401
|
} catch {
|
|
@@ -11423,19 +12420,19 @@ function watchRecursive(root, listener) {
|
|
|
11423
12420
|
return handle;
|
|
11424
12421
|
}
|
|
11425
12422
|
function attachWhenDirExists(dirPath, attach, setWatcher) {
|
|
11426
|
-
if (
|
|
12423
|
+
if (existsSync9(dirPath)) {
|
|
11427
12424
|
setWatcher(attach());
|
|
11428
12425
|
return;
|
|
11429
12426
|
}
|
|
11430
12427
|
const parentDir = dirname2(dirPath);
|
|
11431
12428
|
const targetName = basename2(dirPath);
|
|
11432
|
-
if (!
|
|
12429
|
+
if (!existsSync9(parentDir)) {
|
|
11433
12430
|
return;
|
|
11434
12431
|
}
|
|
11435
12432
|
let parentWatcher = null;
|
|
11436
12433
|
parentWatcher = watch(parentDir, (_event, filename) => {
|
|
11437
12434
|
if (filename !== targetName) return;
|
|
11438
|
-
if (!
|
|
12435
|
+
if (!existsSync9(dirPath)) return;
|
|
11439
12436
|
parentWatcher?.close();
|
|
11440
12437
|
parentWatcher = null;
|
|
11441
12438
|
setWatcher(attach());
|
|
@@ -11451,6 +12448,7 @@ var FileWatcher = class {
|
|
|
11451
12448
|
templatesWatcher = null;
|
|
11452
12449
|
colorsWatcher = null;
|
|
11453
12450
|
variablesWatcher = null;
|
|
12451
|
+
themeCssWatcher = null;
|
|
11454
12452
|
enumsWatcher = null;
|
|
11455
12453
|
cmsWatcher = null;
|
|
11456
12454
|
imagesWatcher = null;
|
|
@@ -11465,7 +12463,7 @@ var FileWatcher = class {
|
|
|
11465
12463
|
* Watches both .json and .js files to detect component definition and JavaScript changes
|
|
11466
12464
|
*/
|
|
11467
12465
|
watchComponents(dirPath = projectPaths.components()) {
|
|
11468
|
-
if (!
|
|
12466
|
+
if (!existsSync9(dirPath)) {
|
|
11469
12467
|
return;
|
|
11470
12468
|
}
|
|
11471
12469
|
this.componentsWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11481,7 +12479,7 @@ var FileWatcher = class {
|
|
|
11481
12479
|
* Start watching pages directory
|
|
11482
12480
|
*/
|
|
11483
12481
|
watchPages(dirPath = projectPaths.pages()) {
|
|
11484
|
-
if (!
|
|
12482
|
+
if (!existsSync9(dirPath)) {
|
|
11485
12483
|
return;
|
|
11486
12484
|
}
|
|
11487
12485
|
this.pagesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11521,7 +12519,7 @@ var FileWatcher = class {
|
|
|
11521
12519
|
* Start watching colors.json file
|
|
11522
12520
|
*/
|
|
11523
12521
|
watchColors(filePath = projectPaths.colors()) {
|
|
11524
|
-
if (!
|
|
12522
|
+
if (!existsSync9(filePath)) {
|
|
11525
12523
|
return;
|
|
11526
12524
|
}
|
|
11527
12525
|
const dirPath = getProjectRoot();
|
|
@@ -11542,6 +12540,33 @@ var FileWatcher = class {
|
|
|
11542
12540
|
}
|
|
11543
12541
|
});
|
|
11544
12542
|
}
|
|
12543
|
+
/**
|
|
12544
|
+
* Start watching `src/styles/theme.css` — the token source of truth for
|
|
12545
|
+
* astro-format projects, where colors AND variables share the one file
|
|
12546
|
+
* (colors.json / variables.json were migrated away, so the two watchers
|
|
12547
|
+
* above never fire there). An external edit (e.g. an AI tool adding tokens)
|
|
12548
|
+
* must invalidate both service caches and refresh connected clients, or the
|
|
12549
|
+
* new tokens only appear after a dev-server restart. Deferred attach:
|
|
12550
|
+
* `src/styles` may not exist yet (theme.css lands later via migration or an
|
|
12551
|
+
* external tool). No-op in legacy JSON projects (no `src/`).
|
|
12552
|
+
*/
|
|
12553
|
+
watchThemeCss(dirPath = join7(getProjectRoot(), "src", "styles")) {
|
|
12554
|
+
attachWhenDirExists(
|
|
12555
|
+
dirPath,
|
|
12556
|
+
() => watch(dirPath, { recursive: false }, async (_event, filename) => {
|
|
12557
|
+
if (filename !== "theme.css") return;
|
|
12558
|
+
if (this.callbacks.onColorsChange) {
|
|
12559
|
+
await this.callbacks.onColorsChange();
|
|
12560
|
+
}
|
|
12561
|
+
if (this.callbacks.onVariablesChange) {
|
|
12562
|
+
await this.callbacks.onVariablesChange();
|
|
12563
|
+
}
|
|
12564
|
+
}),
|
|
12565
|
+
(w) => {
|
|
12566
|
+
this.themeCssWatcher = w;
|
|
12567
|
+
}
|
|
12568
|
+
);
|
|
12569
|
+
}
|
|
11545
12570
|
/**
|
|
11546
12571
|
* Start watching enums.json file
|
|
11547
12572
|
*/
|
|
@@ -11578,7 +12603,7 @@ var FileWatcher = class {
|
|
|
11578
12603
|
* Start watching images directory for new source images
|
|
11579
12604
|
*/
|
|
11580
12605
|
watchImages(dirPath = projectPaths.images()) {
|
|
11581
|
-
if (!
|
|
12606
|
+
if (!existsSync9(dirPath)) {
|
|
11582
12607
|
return;
|
|
11583
12608
|
}
|
|
11584
12609
|
this.imagesWatcher = watch(dirPath, { recursive: false }, async (event, filename) => {
|
|
@@ -11605,7 +12630,7 @@ var FileWatcher = class {
|
|
|
11605
12630
|
* Start watching libraries directory for CSS/JS changes
|
|
11606
12631
|
*/
|
|
11607
12632
|
watchLibraries(dirPath = projectPaths.libraries()) {
|
|
11608
|
-
if (!
|
|
12633
|
+
if (!existsSync9(dirPath)) {
|
|
11609
12634
|
return;
|
|
11610
12635
|
}
|
|
11611
12636
|
this.librariesWatcher = watchRecursive(dirPath, async (_event, filename) => {
|
|
@@ -11638,7 +12663,8 @@ var FileWatcher = class {
|
|
|
11638
12663
|
if (this.callbacks.onCMSTemplateChange) await this.callbacks.onCMSTemplateChange();
|
|
11639
12664
|
return;
|
|
11640
12665
|
}
|
|
11641
|
-
const
|
|
12666
|
+
const pageName = collapseFolderIndex(filename.replace(/\.astro$/, ""));
|
|
12667
|
+
const pagePath = mapPageNameToPath(pageName);
|
|
11642
12668
|
if (this.callbacks.onPageChange) await this.callbacks.onPageChange(pagePath);
|
|
11643
12669
|
}),
|
|
11644
12670
|
(w) => {
|
|
@@ -11682,6 +12708,7 @@ var FileWatcher = class {
|
|
|
11682
12708
|
this.watchTemplates();
|
|
11683
12709
|
this.watchColors();
|
|
11684
12710
|
this.watchVariables();
|
|
12711
|
+
this.watchThemeCss();
|
|
11685
12712
|
this.watchEnums();
|
|
11686
12713
|
this.watchCMS();
|
|
11687
12714
|
this.watchImages();
|
|
@@ -11715,6 +12742,10 @@ var FileWatcher = class {
|
|
|
11715
12742
|
this.variablesWatcher.close();
|
|
11716
12743
|
this.variablesWatcher = null;
|
|
11717
12744
|
}
|
|
12745
|
+
if (this.themeCssWatcher) {
|
|
12746
|
+
this.themeCssWatcher.close();
|
|
12747
|
+
this.themeCssWatcher = null;
|
|
12748
|
+
}
|
|
11718
12749
|
if (this.enumsWatcher) {
|
|
11719
12750
|
this.enumsWatcher.close();
|
|
11720
12751
|
this.enumsWatcher = null;
|
|
@@ -11806,9 +12837,9 @@ var FileWatcherService = class {
|
|
|
11806
12837
|
this.refreshSchemasTimer = null;
|
|
11807
12838
|
await cmsService.refreshSchemas();
|
|
11808
12839
|
const ids = new Set(cmsService.getAllSchemas().keys());
|
|
11809
|
-
for (const
|
|
11810
|
-
if (
|
|
11811
|
-
await pageService.deletePage(
|
|
12840
|
+
for (const path5 of pageService.getAllPagePaths()) {
|
|
12841
|
+
if (path5.startsWith("/templates/") && !ids.has(path5.slice("/templates/".length))) {
|
|
12842
|
+
await pageService.deletePage(path5);
|
|
11812
12843
|
}
|
|
11813
12844
|
}
|
|
11814
12845
|
for (const id of ids) {
|
|
@@ -11862,20 +12893,20 @@ var FileWatcherService = class {
|
|
|
11862
12893
|
};
|
|
11863
12894
|
|
|
11864
12895
|
// lib/server/migrateTemplates.ts
|
|
11865
|
-
import { existsSync as
|
|
12896
|
+
import { existsSync as existsSync10 } from "node:fs";
|
|
11866
12897
|
import { rename } from "node:fs/promises";
|
|
11867
12898
|
import { join as join8 } from "node:path";
|
|
11868
12899
|
async function migrateTemplatesDirectory() {
|
|
11869
12900
|
const oldDir = join8(projectPaths.pages(), "templates");
|
|
11870
12901
|
const newDir = projectPaths.templates();
|
|
11871
|
-
if (!
|
|
11872
|
-
if (
|
|
12902
|
+
if (!existsSync10(oldDir)) return;
|
|
12903
|
+
if (existsSync10(newDir)) return;
|
|
11873
12904
|
await rename(oldDir, newDir);
|
|
11874
12905
|
console.log("Migrated CMS templates: pages/templates/ \u2192 templates/");
|
|
11875
12906
|
}
|
|
11876
12907
|
|
|
11877
12908
|
// build-astro.ts
|
|
11878
|
-
import { existsSync as
|
|
12909
|
+
import { existsSync as existsSync11, readdirSync as readdirSync7, mkdirSync as mkdirSync4, rmSync, statSync as statSync3, copyFileSync, writeFileSync } from "node:fs";
|
|
11879
12910
|
import { writeFile as writeFile2, readFile } from "node:fs/promises";
|
|
11880
12911
|
import { join as join9 } from "node:path";
|
|
11881
12912
|
import { createHash } from "node:crypto";
|
|
@@ -12961,8 +13992,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12961
13992
|
if (dynLiteralMatch) {
|
|
12962
13993
|
const existing = dynLiteralMatch[1] ?? "";
|
|
12963
13994
|
const trimmed = existing.trimEnd();
|
|
12964
|
-
const
|
|
12965
|
-
return ` style={\`${existing}${
|
|
13995
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
13996
|
+
return ` style={\`${existing}${sep2}${extraCss}\`}`;
|
|
12966
13997
|
}
|
|
12967
13998
|
const dynExprMatch = styleAttr.match(/^ style=\{([\s\S]*)\}$/);
|
|
12968
13999
|
if (dynExprMatch) {
|
|
@@ -12970,8 +14001,8 @@ function injectInlineStyle(styleAttr, extraCss) {
|
|
|
12970
14001
|
}
|
|
12971
14002
|
return styleAttr.replace(/style="([^"]*)"/, (_, existing) => {
|
|
12972
14003
|
const trimmed = existing.trimEnd();
|
|
12973
|
-
const
|
|
12974
|
-
return `style="${existing}${
|
|
14004
|
+
const sep2 = trimmed.length > 0 && !trimmed.endsWith(";") ? ";" : "";
|
|
14005
|
+
return `style="${existing}${sep2}${extraCss}"`;
|
|
12975
14006
|
});
|
|
12976
14007
|
}
|
|
12977
14008
|
var PICTURE_WIDTHS = [500, 800, 1080, 1600, 2400];
|
|
@@ -13697,7 +14728,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13697
14728
|
const code = localeConfig.code;
|
|
13698
14729
|
const isCurrent = code === currentLocale;
|
|
13699
14730
|
if (!showCurrent && isCurrent) continue;
|
|
13700
|
-
const
|
|
14731
|
+
const path5 = slugMap[code] || "/";
|
|
13701
14732
|
const classes = isCurrent ? activeItemClasses : itemClasses;
|
|
13702
14733
|
const classAttr = classes.length > 0 ? ` class="${classes.join(" ")}"` : "";
|
|
13703
14734
|
const currentAttr = isCurrent ? ' data-current="true"' : ' data-current="false"';
|
|
@@ -13722,7 +14753,7 @@ function emitLocaleListNode(node, ctx) {
|
|
|
13722
14753
|
}
|
|
13723
14754
|
linkContent += `<div>${escapeJSX(displayText)}</div>`;
|
|
13724
14755
|
links.push(
|
|
13725
|
-
`${ind(ctx)} <a href="${escapeJSX(
|
|
14756
|
+
`${ind(ctx)} <a href="${escapeJSX(path5)}"${hreflangAttr}${currentAttr} data-locale="${escapeJSX(code)}"${classAttr}>${linkContent}</a>`
|
|
13726
14757
|
);
|
|
13727
14758
|
}
|
|
13728
14759
|
let linksContent;
|
|
@@ -14216,8 +15247,8 @@ function emitAstroPage(options) {
|
|
|
14216
15247
|
}
|
|
14217
15248
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14218
15249
|
for (const comp of componentImports) {
|
|
14219
|
-
const
|
|
14220
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15250
|
+
const path5 = componentImportPath(fileDepth, comp);
|
|
15251
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14221
15252
|
}
|
|
14222
15253
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
14223
15254
|
const libraryTagsLiteral = `{ headCSS: \`${escapeTemplateLiteral2(libraryTags.headCSS || "")}\`, headJS: \`${escapeTemplateLiteral2(libraryTags.headJS || "")}\`, bodyEndJS: \`${escapeTemplateLiteral2(libraryTags.bodyEndJS || "")}\` }`;
|
|
@@ -14556,8 +15587,8 @@ function emitCMSPage(options) {
|
|
|
14556
15587
|
}
|
|
14557
15588
|
const componentImports = Array.from(ctx.imports).sort();
|
|
14558
15589
|
for (const comp of componentImports) {
|
|
14559
|
-
const
|
|
14560
|
-
importLines.push(`import ${astroComponentName(comp)} from '${
|
|
15590
|
+
const path5 = componentImportPath2(fileDepth, comp);
|
|
15591
|
+
importLines.push(`import ${astroComponentName(comp)} from '${path5}';`);
|
|
14561
15592
|
}
|
|
14562
15593
|
const staticPaths = buildGetStaticPaths(cmsSchema, isMultiLocale, i18nConfig, locale);
|
|
14563
15594
|
const scriptsArrayLiteral = scriptPaths.length > 0 ? `[${scriptPaths.map((s) => `"${s}"`).join(", ")}]` : "[]";
|
|
@@ -14730,24 +15761,24 @@ function writePageScript(javascript, scriptsDir) {
|
|
|
14730
15761
|
if (!javascript) return [];
|
|
14731
15762
|
const hash = hashContent3(javascript);
|
|
14732
15763
|
const scriptFile = `${hash}.js`;
|
|
14733
|
-
if (!
|
|
15764
|
+
if (!existsSync11(scriptsDir)) {
|
|
14734
15765
|
mkdirSync4(scriptsDir, { recursive: true });
|
|
14735
15766
|
}
|
|
14736
15767
|
const fullScriptPath = join9(scriptsDir, scriptFile);
|
|
14737
|
-
if (!
|
|
15768
|
+
if (!existsSync11(fullScriptPath)) {
|
|
14738
15769
|
writeFileSync(fullScriptPath, javascript, "utf-8");
|
|
14739
15770
|
}
|
|
14740
15771
|
return [`/_scripts/${scriptFile}`];
|
|
14741
15772
|
}
|
|
14742
15773
|
function copyDirectory(src, dest, filter) {
|
|
14743
|
-
if (!
|
|
14744
|
-
if (!
|
|
14745
|
-
const files =
|
|
15774
|
+
if (!existsSync11(src)) return;
|
|
15775
|
+
if (!existsSync11(dest)) mkdirSync4(dest, { recursive: true });
|
|
15776
|
+
const files = readdirSync7(src);
|
|
14746
15777
|
for (const file of files) {
|
|
14747
15778
|
if (filter && !filter(file)) continue;
|
|
14748
15779
|
const srcPath = join9(src, file);
|
|
14749
15780
|
const destPath = join9(dest, file);
|
|
14750
|
-
const stat =
|
|
15781
|
+
const stat = statSync3(srcPath);
|
|
14751
15782
|
if (stat.isDirectory()) copyDirectory(srcPath, destPath, filter);
|
|
14752
15783
|
else copyFileSync(srcPath, destPath);
|
|
14753
15784
|
}
|
|
@@ -14763,8 +15794,8 @@ function isCMSPage(pageData) {
|
|
|
14763
15794
|
}
|
|
14764
15795
|
function scanJSONFiles(dir, prefix = "") {
|
|
14765
15796
|
const results = [];
|
|
14766
|
-
if (!
|
|
14767
|
-
const entries =
|
|
15797
|
+
if (!existsSync11(dir)) return results;
|
|
15798
|
+
const entries = readdirSync7(dir, { withFileTypes: true });
|
|
14768
15799
|
for (const entry of entries) {
|
|
14769
15800
|
if (entry.isFile() && entry.name.endsWith(".json")) {
|
|
14770
15801
|
results.push(prefix ? `${prefix}/${entry.name}` : entry.name);
|
|
@@ -14936,7 +15967,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14936
15967
|
const componentLibraries = collectComponentLibraries(globalComponents);
|
|
14937
15968
|
const imageMetadataMap = await buildImageMetadataMap();
|
|
14938
15969
|
const outDir = outputDir || join9(projectPaths.project, "astro-export");
|
|
14939
|
-
if (
|
|
15970
|
+
if (existsSync11(outDir)) {
|
|
14940
15971
|
rmSync(outDir, { recursive: true, force: true });
|
|
14941
15972
|
}
|
|
14942
15973
|
mkdirSync4(outDir, { recursive: true });
|
|
@@ -14951,7 +15982,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
14951
15982
|
mkdirSync4(d, { recursive: true });
|
|
14952
15983
|
}
|
|
14953
15984
|
const pagesDir = projectPaths.pages();
|
|
14954
|
-
if (!
|
|
15985
|
+
if (!existsSync11(pagesDir)) {
|
|
14955
15986
|
console.error("Pages directory not found!");
|
|
14956
15987
|
return { pages: 0, cmsPages: 0, collections: 0, errors: 1 };
|
|
14957
15988
|
}
|
|
@@ -15082,7 +16113,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15082
16113
|
const shouldInline = css.inline !== false;
|
|
15083
16114
|
const relPath = css.url.slice(1);
|
|
15084
16115
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15085
|
-
if (!
|
|
16116
|
+
if (!existsSync11(srcPath)) continue;
|
|
15086
16117
|
if (shouldInline) {
|
|
15087
16118
|
try {
|
|
15088
16119
|
inlineContents.set(css.url, await readFile(srcPath, "utf-8"));
|
|
@@ -15096,7 +16127,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15096
16127
|
for (const js of buildLibraries.js || []) {
|
|
15097
16128
|
if (js.url.startsWith("/")) {
|
|
15098
16129
|
const relPath = js.url.slice(1);
|
|
15099
|
-
if (
|
|
16130
|
+
if (existsSync11(join9(projectPaths.project, relPath))) {
|
|
15100
16131
|
localLibsToCopy.push(relPath);
|
|
15101
16132
|
}
|
|
15102
16133
|
}
|
|
@@ -15117,8 +16148,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15117
16148
|
const cmsConsumerComponents = computeCmsConsumerComponents(globalComponents);
|
|
15118
16149
|
const collectionUrlExpr = /* @__PURE__ */ new Map();
|
|
15119
16150
|
const mergedRichTextFields = /* @__PURE__ */ new Set();
|
|
15120
|
-
if (
|
|
15121
|
-
for (const file of
|
|
16151
|
+
if (existsSync11(templatesDir)) {
|
|
16152
|
+
for (const file of readdirSync7(templatesDir).filter((f) => f.endsWith(".json"))) {
|
|
15122
16153
|
const tc = await loadJSONFile(join9(templatesDir, file));
|
|
15123
16154
|
if (!tc) continue;
|
|
15124
16155
|
try {
|
|
@@ -15133,8 +16164,8 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15133
16164
|
}
|
|
15134
16165
|
}
|
|
15135
16166
|
}
|
|
15136
|
-
if (
|
|
15137
|
-
const templateFiles =
|
|
16167
|
+
if (existsSync11(templatesDir)) {
|
|
16168
|
+
const templateFiles = readdirSync7(templatesDir).filter((f) => f.endsWith(".json"));
|
|
15138
16169
|
for (const file of templateFiles) {
|
|
15139
16170
|
const templateContent = await loadJSONFile(join9(templatesDir, file));
|
|
15140
16171
|
if (!templateContent) continue;
|
|
@@ -15227,7 +16258,7 @@ async function buildAstroProject(_projectRoot, outputDir) {
|
|
|
15227
16258
|
});
|
|
15228
16259
|
const astroFileFull = join9(pagesOutDir, astroFilePath);
|
|
15229
16260
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15230
|
-
if (!
|
|
16261
|
+
if (!existsSync11(astroFileDir)) {
|
|
15231
16262
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15232
16263
|
}
|
|
15233
16264
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15378,7 +16409,7 @@ const { title, meta = '', scripts = [], locale = 'en', theme = '${themeConfig.de
|
|
|
15378
16409
|
}
|
|
15379
16410
|
const astroFileFull = join9(pagesOutDir, result.astroFilePath);
|
|
15380
16411
|
const astroFileDir = astroFileFull.substring(0, astroFileFull.lastIndexOf("/"));
|
|
15381
|
-
if (!
|
|
16412
|
+
if (!existsSync11(astroFileDir)) {
|
|
15382
16413
|
mkdirSync4(astroFileDir, { recursive: true });
|
|
15383
16414
|
}
|
|
15384
16415
|
await writeFile2(astroFileFull, astroContent, "utf-8");
|
|
@@ -15410,9 +16441,9 @@ export const GET: APIRoute = () => {
|
|
|
15410
16441
|
mkdirSync4(collectionDir, { recursive: true });
|
|
15411
16442
|
const richTextFieldNames = Object.entries(schema.fields || {}).filter(([, fd]) => fd.type === "rich-text").map(([fn]) => fn);
|
|
15412
16443
|
const cmsItemsDir = join9(projectPaths.cms(), schema.id);
|
|
15413
|
-
if (
|
|
16444
|
+
if (existsSync11(cmsItemsDir)) {
|
|
15414
16445
|
const isDevBuild = process.env.MENO_DEV_BUILD === "true";
|
|
15415
|
-
const itemFiles =
|
|
16446
|
+
const itemFiles = readdirSync7(cmsItemsDir).filter(
|
|
15416
16447
|
(f) => f.endsWith(".json") && (isDevBuild || !f.endsWith(`${CMS_DRAFT_SUFFIX}.json`))
|
|
15417
16448
|
);
|
|
15418
16449
|
for (const itemFile of itemFiles) {
|
|
@@ -15459,26 +16490,26 @@ export { collections };
|
|
|
15459
16490
|
await writeFile2(join9(srcDir, "content.config.ts"), configContent, "utf-8");
|
|
15460
16491
|
}
|
|
15461
16492
|
const imagesSrcDir = join9(projectPaths.project, "images");
|
|
15462
|
-
if (
|
|
16493
|
+
if (existsSync11(imagesSrcDir)) {
|
|
15463
16494
|
copyDirectory(imagesSrcDir, join9(srcDir, "assets", "images"), shouldCopyImageForAstro);
|
|
15464
16495
|
copyDirectory(imagesSrcDir, join9(publicDir, "images"));
|
|
15465
16496
|
}
|
|
15466
16497
|
const publicAssetDirs = ["fonts", "icons", "videos", "assets"];
|
|
15467
16498
|
for (const dir of publicAssetDirs) {
|
|
15468
16499
|
const srcAssetDir = join9(projectPaths.project, dir);
|
|
15469
|
-
if (
|
|
16500
|
+
if (existsSync11(srcAssetDir)) {
|
|
15470
16501
|
copyDirectory(srcAssetDir, join9(publicDir, dir));
|
|
15471
16502
|
}
|
|
15472
16503
|
}
|
|
15473
16504
|
const librariesDir = join9(projectPaths.project, "libraries");
|
|
15474
|
-
if (
|
|
16505
|
+
if (existsSync11(librariesDir)) {
|
|
15475
16506
|
copyDirectory(librariesDir, join9(publicDir, "libraries"));
|
|
15476
16507
|
}
|
|
15477
16508
|
for (const relPath of localLibsToCopy) {
|
|
15478
16509
|
const srcPath = join9(projectPaths.project, relPath);
|
|
15479
16510
|
const destPath = join9(publicDir, relPath);
|
|
15480
16511
|
const destDir = destPath.substring(0, destPath.lastIndexOf("/"));
|
|
15481
|
-
if (destDir && !
|
|
16512
|
+
if (destDir && !existsSync11(destDir)) mkdirSync4(destDir, { recursive: true });
|
|
15482
16513
|
copyFileSync(srcPath, destPath);
|
|
15483
16514
|
}
|
|
15484
16515
|
const packageJson = {
|
|
@@ -15548,6 +16579,32 @@ export default defineConfig({${siteUrl ? `
|
|
|
15548
16579
|
|
|
15549
16580
|
// lib/server/index.ts
|
|
15550
16581
|
init_constants();
|
|
16582
|
+
|
|
16583
|
+
// lib/server/webflow/templateWrapper.ts
|
|
16584
|
+
import { readFile as readFile2 } from "node:fs/promises";
|
|
16585
|
+
var cachedTemplate = null;
|
|
16586
|
+
async function getWebflowTemplate(appName) {
|
|
16587
|
+
if (cachedTemplate) return cachedTemplate;
|
|
16588
|
+
const url = `https://webflow-ext.com/template/v2?name=${encodeURIComponent(appName)}`;
|
|
16589
|
+
const res = await fetch(url);
|
|
16590
|
+
if (!res.ok) throw new Error(`Failed to fetch Webflow template: ${res.status}`);
|
|
16591
|
+
cachedTemplate = await res.text();
|
|
16592
|
+
return cachedTemplate;
|
|
16593
|
+
}
|
|
16594
|
+
async function wrapInWebflowTemplate(html, manifestPath) {
|
|
16595
|
+
try {
|
|
16596
|
+
const manifest = JSON.parse(await readFile2(manifestPath, "utf-8"));
|
|
16597
|
+
const template = await getWebflowTemplate(manifest.name || "Export to Meno");
|
|
16598
|
+
const headMatch = html.match(/<head[^>]*>([\s\S]*?)<\/head>/i);
|
|
16599
|
+
const bodyMatch = html.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
|
|
16600
|
+
const headContent = headMatch?.[1] ?? "";
|
|
16601
|
+
const bodyContent = bodyMatch?.[1] ?? "";
|
|
16602
|
+
return template.replace("{{ui}}", headContent + bodyContent);
|
|
16603
|
+
} catch (err) {
|
|
16604
|
+
console.warn("Could not fetch Webflow wrapper template:", err.message);
|
|
16605
|
+
return html;
|
|
16606
|
+
}
|
|
16607
|
+
}
|
|
15551
16608
|
export {
|
|
15552
16609
|
CMSService,
|
|
15553
16610
|
ColorService,
|
|
@@ -15567,6 +16624,7 @@ export {
|
|
|
15567
16624
|
SERVE_PORT,
|
|
15568
16625
|
VariableService,
|
|
15569
16626
|
WebSocketManager,
|
|
16627
|
+
auditStylesheet,
|
|
15570
16628
|
buildAstroProject,
|
|
15571
16629
|
buildAttributes,
|
|
15572
16630
|
buildComponentHTML,
|
|
@@ -15574,6 +16632,7 @@ export {
|
|
|
15574
16632
|
buildLineMap,
|
|
15575
16633
|
bundleFile,
|
|
15576
16634
|
clearJSValidationCache,
|
|
16635
|
+
collectClassesFromPageData,
|
|
15577
16636
|
collectComponentCSS,
|
|
15578
16637
|
collectComponentJavaScript,
|
|
15579
16638
|
colorService,
|
|
@@ -15588,6 +16647,7 @@ export {
|
|
|
15588
16647
|
errorResponse,
|
|
15589
16648
|
escapeHtml,
|
|
15590
16649
|
extractPageMeta,
|
|
16650
|
+
extractRules,
|
|
15591
16651
|
fileExists,
|
|
15592
16652
|
formHandlerScript,
|
|
15593
16653
|
generateBuildErrorPage,
|
|
@@ -15634,12 +16694,14 @@ export {
|
|
|
15634
16694
|
readJsonFile,
|
|
15635
16695
|
readTextFile,
|
|
15636
16696
|
regenerateThemeCss,
|
|
16697
|
+
renderAuditMarkdown,
|
|
15637
16698
|
renderPageSSR,
|
|
15638
16699
|
resetFontConfig,
|
|
15639
16700
|
resolvePackagePath,
|
|
15640
16701
|
resolveProjectPath,
|
|
15641
16702
|
saveColors,
|
|
15642
16703
|
saveVariables,
|
|
16704
|
+
sectionPrefix,
|
|
15643
16705
|
serializeThemeCss,
|
|
15644
16706
|
serveFile,
|
|
15645
16707
|
setProjectRoot,
|
|
@@ -15651,6 +16713,7 @@ export {
|
|
|
15651
16713
|
watchRecursive,
|
|
15652
16714
|
withErrorHandling,
|
|
15653
16715
|
withLogging,
|
|
16716
|
+
wrapInWebflowTemplate,
|
|
15654
16717
|
writeFile
|
|
15655
16718
|
};
|
|
15656
16719
|
//# sourceMappingURL=index.js.map
|