veryfront 0.1.89 → 0.1.91
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/esm/cli/commands/knowledge/command.d.ts +3 -0
- package/esm/cli/commands/knowledge/command.d.ts.map +1 -1
- package/esm/cli/commands/knowledge/command.js +27 -2
- package/esm/deno.js +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts +5 -1
- package/esm/src/html/styles-builder/candidate-extractor.d.ts.map +1 -1
- package/esm/src/html/styles-builder/candidate-extractor.js +6 -1
- package/esm/src/html/styles-builder/content-version.d.ts +9 -0
- package/esm/src/html/styles-builder/content-version.d.ts.map +1 -0
- package/esm/src/html/styles-builder/content-version.js +15 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts +11 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -1
- package/esm/src/html/styles-builder/css-pregeneration.js +15 -10
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts +31 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.d.ts.map +1 -0
- package/esm/src/html/styles-builder/prepared-project-css-cache.js +165 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts +14 -0
- package/esm/src/html/styles-builder/style-scope-profile.d.ts.map +1 -0
- package/esm/src/html/styles-builder/style-scope-profile.js +121 -0
- package/esm/src/jobs/schemas.d.ts +30 -30
- package/esm/src/platform/adapters/fs/factory.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/factory.js +2 -21
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +44 -19
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts +3 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.d.ts.map +1 -0
- package/esm/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.js +25 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +2 -15
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +4 -0
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +2 -0
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +3 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.js +6 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +2 -2
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/index.js +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +24 -0
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.js +65 -3
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +28 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +13 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/index.js +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +3 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.js +10 -5
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +8 -0
- package/esm/src/sandbox/index.d.ts +1 -1
- package/esm/src/sandbox/index.d.ts.map +1 -1
- package/esm/src/sandbox/index.js +1 -1
- package/esm/src/sandbox/sandbox.d.ts +58 -0
- package/esm/src/sandbox/sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.js +111 -0
- package/esm/src/server/handlers/dev/styles-candidate-scanner.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-candidate-scanner.js +14 -16
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +7 -0
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.js +175 -8
- package/package.json +1 -1
- package/src/cli/commands/knowledge/command.ts +30 -2
- package/src/deno.js +1 -1
- package/src/src/html/styles-builder/candidate-extractor.ts +13 -0
- package/src/src/html/styles-builder/content-version.ts +20 -0
- package/src/src/html/styles-builder/css-pregeneration.ts +49 -12
- package/src/src/html/styles-builder/prepared-project-css-cache.ts +228 -0
- package/src/src/html/styles-builder/style-scope-profile.ts +164 -0
- package/src/src/platform/adapters/fs/factory.ts +2 -27
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +49 -20
- package/src/src/platform/adapters/fs/veryfront/default-invalidation-callbacks.ts +35 -0
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +4 -21
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +3 -0
- package/src/src/platform/adapters/veryfront-api-client/client.ts +17 -0
- package/src/src/platform/adapters/veryfront-api-client/index.ts +6 -0
- package/src/src/platform/adapters/veryfront-api-client/operations.ts +110 -3
- package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +16 -0
- package/src/src/platform/adapters/veryfront-api-client/schemas/index.ts +2 -0
- package/src/src/rendering/orchestrator/css-candidate-manifest.ts +28 -6
- package/src/src/rendering/orchestrator/html.ts +11 -0
- package/src/src/sandbox/index.ts +13 -1
- package/src/src/sandbox/sandbox.ts +183 -0
- package/src/src/server/handlers/dev/styles-candidate-scanner.ts +18 -15
- package/src/src/server/handlers/dev/styles-css.handler.ts +262 -12
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import {
|
|
2
|
+
type CacheBackend,
|
|
3
|
+
createCacheBackend,
|
|
4
|
+
MemoryCacheBackend,
|
|
5
|
+
} from "../../cache/backend.js";
|
|
6
|
+
import { registerCache } from "../../utils/memory/index.js";
|
|
7
|
+
import { serverLogger } from "../../utils/index.js";
|
|
8
|
+
import { DEFAULT_STYLESHEET } from "./css-hash-cache.js";
|
|
9
|
+
import { resolveStylesheet } from "./tailwind-compiler-utils.js";
|
|
10
|
+
|
|
11
|
+
const logger = serverLogger.component("prepared-project-css-cache");
|
|
12
|
+
|
|
13
|
+
interface PreparedProjectCSSCacheEntry {
|
|
14
|
+
css: string;
|
|
15
|
+
hash: string;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
interface PreparedProjectCSSLocalEntry extends PreparedProjectCSSCacheEntry {
|
|
19
|
+
expiresAt: number;
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
interface PreparedProjectCSSProfile {
|
|
23
|
+
minify?: boolean;
|
|
24
|
+
environment?: string;
|
|
25
|
+
buildMode?: "development" | "production";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export interface PreparedProjectCSSRequestContext {
|
|
29
|
+
projectSlug: string;
|
|
30
|
+
projectVersion: string;
|
|
31
|
+
stylesheet: string;
|
|
32
|
+
stylesheetHash: string;
|
|
33
|
+
styleProfileHash: string;
|
|
34
|
+
environment: string;
|
|
35
|
+
profileHash: string;
|
|
36
|
+
cacheKey: string;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const PREPARED_PROJECT_CSS_CACHE_TTL_SECONDS = 24 * 3600;
|
|
40
|
+
const PREPARED_PROJECT_CSS_LOCAL_MAX = 50;
|
|
41
|
+
const PREPARED_PROJECT_CSS_LOCAL_TTL_MS = PREPARED_PROJECT_CSS_CACHE_TTL_SECONDS * 1000;
|
|
42
|
+
|
|
43
|
+
let preparedProjectCSSBackend: CacheBackend | null = null;
|
|
44
|
+
let preparedProjectCSSInitialized = false;
|
|
45
|
+
let preparedProjectCSSInitPromise: Promise<void> | null = null;
|
|
46
|
+
|
|
47
|
+
const localPreparedProjectCSS = new Map<string, PreparedProjectCSSLocalEntry>();
|
|
48
|
+
|
|
49
|
+
registerCache("prepared-project-css-cache", () => ({
|
|
50
|
+
name: "prepared-project-css-cache",
|
|
51
|
+
entries: localPreparedProjectCSS.size,
|
|
52
|
+
maxEntries: PREPARED_PROJECT_CSS_LOCAL_MAX,
|
|
53
|
+
backend: preparedProjectCSSBackend?.type ?? "uninitialized",
|
|
54
|
+
}));
|
|
55
|
+
|
|
56
|
+
function hashValue(input: string): string {
|
|
57
|
+
let hash = 0;
|
|
58
|
+
for (let index = 0; index < input.length; index++) {
|
|
59
|
+
hash = ((hash << 5) - hash) + input.charCodeAt(index);
|
|
60
|
+
hash |= 0;
|
|
61
|
+
}
|
|
62
|
+
return hash.toString(36);
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function setLocalEntry(key: string, entry: PreparedProjectCSSCacheEntry): void {
|
|
66
|
+
localPreparedProjectCSS.set(key, {
|
|
67
|
+
...entry,
|
|
68
|
+
expiresAt: Date.now() + PREPARED_PROJECT_CSS_LOCAL_TTL_MS,
|
|
69
|
+
});
|
|
70
|
+
|
|
71
|
+
if (localPreparedProjectCSS.size <= PREPARED_PROJECT_CSS_LOCAL_MAX) return;
|
|
72
|
+
|
|
73
|
+
const keys = localPreparedProjectCSS.keys();
|
|
74
|
+
while (localPreparedProjectCSS.size > PREPARED_PROJECT_CSS_LOCAL_MAX) {
|
|
75
|
+
const result = keys.next();
|
|
76
|
+
if (result.done) break;
|
|
77
|
+
localPreparedProjectCSS.delete(result.value);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
function parsePreparedProjectCSSCacheEntry(
|
|
82
|
+
raw: string,
|
|
83
|
+
): PreparedProjectCSSCacheEntry | null {
|
|
84
|
+
try {
|
|
85
|
+
const parsed = JSON.parse(raw) as Partial<PreparedProjectCSSCacheEntry>;
|
|
86
|
+
if (typeof parsed.css !== "string" || typeof parsed.hash !== "string") return null;
|
|
87
|
+
return { css: parsed.css, hash: parsed.hash };
|
|
88
|
+
} catch {
|
|
89
|
+
return null;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
export async function initializePreparedProjectCSSCache(): Promise<boolean> {
|
|
94
|
+
if (preparedProjectCSSInitialized) return preparedProjectCSSBackend?.type !== "memory";
|
|
95
|
+
|
|
96
|
+
if (!preparedProjectCSSInitPromise) {
|
|
97
|
+
preparedProjectCSSInitPromise = (async () => {
|
|
98
|
+
try {
|
|
99
|
+
preparedProjectCSSBackend = await createCacheBackend({
|
|
100
|
+
keyPrefix: "prepared-project-css",
|
|
101
|
+
});
|
|
102
|
+
logger.debug("Initialized", { backend: preparedProjectCSSBackend.type });
|
|
103
|
+
} catch (error) {
|
|
104
|
+
logger.warn("Backend init failed, using memory", { error });
|
|
105
|
+
preparedProjectCSSBackend = new MemoryCacheBackend(PREPARED_PROJECT_CSS_LOCAL_MAX);
|
|
106
|
+
} finally {
|
|
107
|
+
preparedProjectCSSInitialized = true;
|
|
108
|
+
}
|
|
109
|
+
})();
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
await preparedProjectCSSInitPromise;
|
|
113
|
+
preparedProjectCSSInitPromise = null;
|
|
114
|
+
|
|
115
|
+
return preparedProjectCSSBackend?.type !== "memory";
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
export function createPreparedProjectCSSContext(
|
|
119
|
+
projectSlug: string,
|
|
120
|
+
projectVersion: string,
|
|
121
|
+
stylesheet: string | undefined,
|
|
122
|
+
styleProfileHash: string,
|
|
123
|
+
profile?: PreparedProjectCSSProfile,
|
|
124
|
+
): PreparedProjectCSSRequestContext {
|
|
125
|
+
const resolvedStylesheet = resolveStylesheet(stylesheet, DEFAULT_STYLESHEET);
|
|
126
|
+
const stylesheetHash = hashValue(resolvedStylesheet);
|
|
127
|
+
const environment = profile?.environment ?? "preview";
|
|
128
|
+
const profileHash = hashValue(
|
|
129
|
+
JSON.stringify({
|
|
130
|
+
cacheSchema: "v1",
|
|
131
|
+
minify: profile?.minify ?? false,
|
|
132
|
+
buildMode: profile?.buildMode ?? "production",
|
|
133
|
+
environment,
|
|
134
|
+
}),
|
|
135
|
+
);
|
|
136
|
+
|
|
137
|
+
return {
|
|
138
|
+
projectSlug,
|
|
139
|
+
projectVersion,
|
|
140
|
+
stylesheet: resolvedStylesheet,
|
|
141
|
+
stylesheetHash,
|
|
142
|
+
styleProfileHash,
|
|
143
|
+
environment,
|
|
144
|
+
profileHash,
|
|
145
|
+
cacheKey:
|
|
146
|
+
`${projectSlug}:${environment}:prepared:${projectVersion}:${stylesheetHash}:${styleProfileHash}:${profileHash}`,
|
|
147
|
+
};
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
export async function tryGetPreparedProjectCSS(
|
|
151
|
+
context: PreparedProjectCSSRequestContext,
|
|
152
|
+
): Promise<{ css: string; hash: string; fromCache: true } | undefined> {
|
|
153
|
+
const local = localPreparedProjectCSS.get(context.cacheKey);
|
|
154
|
+
if (local && local.expiresAt > Date.now()) {
|
|
155
|
+
return { css: local.css, hash: local.hash, fromCache: true };
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
if (local) {
|
|
159
|
+
localPreparedProjectCSS.delete(context.cacheKey);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
if (!preparedProjectCSSInitialized) {
|
|
163
|
+
await initializePreparedProjectCSSCache();
|
|
164
|
+
}
|
|
165
|
+
|
|
166
|
+
if (!preparedProjectCSSBackend) return undefined;
|
|
167
|
+
|
|
168
|
+
try {
|
|
169
|
+
const raw = await preparedProjectCSSBackend.get(context.cacheKey);
|
|
170
|
+
if (!raw) return undefined;
|
|
171
|
+
|
|
172
|
+
const entry = parsePreparedProjectCSSCacheEntry(raw);
|
|
173
|
+
if (!entry) return undefined;
|
|
174
|
+
|
|
175
|
+
setLocalEntry(context.cacheKey, entry);
|
|
176
|
+
return { css: entry.css, hash: entry.hash, fromCache: true };
|
|
177
|
+
} catch (error) {
|
|
178
|
+
logger.debug("Failed to read prepared project CSS", {
|
|
179
|
+
cacheKey: context.cacheKey,
|
|
180
|
+
error,
|
|
181
|
+
});
|
|
182
|
+
return undefined;
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
export async function storePreparedProjectCSS(
|
|
187
|
+
context: PreparedProjectCSSRequestContext,
|
|
188
|
+
entry: PreparedProjectCSSCacheEntry,
|
|
189
|
+
): Promise<void> {
|
|
190
|
+
if (!preparedProjectCSSInitialized) {
|
|
191
|
+
await initializePreparedProjectCSSCache();
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
setLocalEntry(context.cacheKey, entry);
|
|
195
|
+
|
|
196
|
+
if (!preparedProjectCSSBackend) return;
|
|
197
|
+
|
|
198
|
+
preparedProjectCSSBackend
|
|
199
|
+
.set(context.cacheKey, JSON.stringify(entry), PREPARED_PROJECT_CSS_CACHE_TTL_SECONDS)
|
|
200
|
+
.catch((error) => {
|
|
201
|
+
logger.debug("Failed to store prepared project CSS", {
|
|
202
|
+
cacheKey: context.cacheKey,
|
|
203
|
+
error,
|
|
204
|
+
});
|
|
205
|
+
});
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
export function invalidatePreparedProjectCSS(projectSlug: string): void {
|
|
209
|
+
for (const key of localPreparedProjectCSS.keys()) {
|
|
210
|
+
if (key.startsWith(`${projectSlug}:`)) {
|
|
211
|
+
localPreparedProjectCSS.delete(key);
|
|
212
|
+
}
|
|
213
|
+
}
|
|
214
|
+
|
|
215
|
+
invalidatePreparedProjectCSSAsync(projectSlug).catch((error) => {
|
|
216
|
+
logger.debug("Failed to invalidate prepared project CSS", { projectSlug, error });
|
|
217
|
+
});
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
export async function invalidatePreparedProjectCSSAsync(projectSlug: string): Promise<void> {
|
|
221
|
+
if (!preparedProjectCSSBackend?.delByPattern) return;
|
|
222
|
+
|
|
223
|
+
try {
|
|
224
|
+
await preparedProjectCSSBackend.delByPattern(`${projectSlug}:*`);
|
|
225
|
+
} catch (error) {
|
|
226
|
+
logger.debug("Failed to delete prepared project CSS", { projectSlug, error });
|
|
227
|
+
}
|
|
228
|
+
}
|
|
@@ -0,0 +1,164 @@
|
|
|
1
|
+
import type { VeryfrontConfig } from "../../config/index.js";
|
|
2
|
+
|
|
3
|
+
const DEFAULT_IGNORED_ROOTS = [
|
|
4
|
+
"knowledge",
|
|
5
|
+
"coverage",
|
|
6
|
+
"dist",
|
|
7
|
+
"build",
|
|
8
|
+
".git",
|
|
9
|
+
"node_modules",
|
|
10
|
+
".cache",
|
|
11
|
+
];
|
|
12
|
+
|
|
13
|
+
const DEFAULT_PROTECTED_ROOTS = [
|
|
14
|
+
"app",
|
|
15
|
+
"pages",
|
|
16
|
+
"components",
|
|
17
|
+
"src/app",
|
|
18
|
+
"src/pages",
|
|
19
|
+
"src/components",
|
|
20
|
+
];
|
|
21
|
+
|
|
22
|
+
export interface StyleScopeProfile {
|
|
23
|
+
hash: string;
|
|
24
|
+
ignoredRoots: string[];
|
|
25
|
+
protectedRoots: string[];
|
|
26
|
+
protectedPaths: string[];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function normalizePath(path: string): string {
|
|
30
|
+
return path.replace(/\\/g, "/").replace(/\/{2,}/g, "/");
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
function normalizeRelativePath(path: string): string {
|
|
34
|
+
return normalizePath(path).replace(/^\/+/, "").replace(/\/+$/, "");
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function toRelativeProjectPath(path: string, projectDir?: string): string {
|
|
38
|
+
const normalized = normalizePath(path);
|
|
39
|
+
const normalizedProjectDir = projectDir
|
|
40
|
+
? normalizePath(projectDir).replace(/\/+$/, "")
|
|
41
|
+
: undefined;
|
|
42
|
+
|
|
43
|
+
if (normalizedProjectDir && normalized.startsWith(normalizedProjectDir)) {
|
|
44
|
+
return normalized.slice(normalizedProjectDir.length).replace(/^\/+/, "");
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
return normalized.replace(/^\/+/, "");
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
function isWithinPath(path: string, root: string): boolean {
|
|
51
|
+
return path === root || path.startsWith(`${root}/`);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
function getParentDirectory(path: string): string | null {
|
|
55
|
+
const normalized = normalizeRelativePath(path);
|
|
56
|
+
const slashIndex = normalized.lastIndexOf("/");
|
|
57
|
+
if (slashIndex <= 0) return null;
|
|
58
|
+
return normalized.slice(0, slashIndex);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function stableHash(input: string): string {
|
|
62
|
+
let hash = 0;
|
|
63
|
+
for (let index = 0; index < input.length; index++) {
|
|
64
|
+
hash = ((hash << 5) - hash) + input.charCodeAt(index);
|
|
65
|
+
hash |= 0;
|
|
66
|
+
}
|
|
67
|
+
return hash.toString(36);
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
function addNormalizedPath(target: Set<string>, value: string | null | undefined): void {
|
|
71
|
+
if (!value) return;
|
|
72
|
+
const normalized = normalizeRelativePath(value);
|
|
73
|
+
if (!normalized) return;
|
|
74
|
+
target.add(normalized);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
export function createStyleScopeProfile(config?: VeryfrontConfig): StyleScopeProfile {
|
|
78
|
+
const ignoredRoots = new Set<string>(DEFAULT_IGNORED_ROOTS);
|
|
79
|
+
const protectedRoots = new Set<string>(DEFAULT_PROTECTED_ROOTS);
|
|
80
|
+
const protectedPaths = new Set<string>();
|
|
81
|
+
|
|
82
|
+
addNormalizedPath(protectedRoots, config?.directories?.app);
|
|
83
|
+
addNormalizedPath(protectedRoots, config?.directories?.pages);
|
|
84
|
+
|
|
85
|
+
for (const path of config?.directories?.components ?? []) {
|
|
86
|
+
addNormalizedPath(protectedRoots, path);
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
const explicitPaths = [
|
|
90
|
+
typeof config?.layout === "string" ? config.layout : undefined,
|
|
91
|
+
typeof config?.app === "string" ? config.app : undefined,
|
|
92
|
+
config?.tailwind?.stylesheet,
|
|
93
|
+
];
|
|
94
|
+
|
|
95
|
+
for (const path of explicitPaths) {
|
|
96
|
+
addNormalizedPath(protectedPaths, path);
|
|
97
|
+
addNormalizedPath(protectedRoots, getParentDirectory(path ?? ""));
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
for (const root of protectedRoots) {
|
|
101
|
+
ignoredRoots.delete(root);
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
const sortedIgnoredRoots = [...ignoredRoots].sort();
|
|
105
|
+
const sortedProtectedRoots = [...protectedRoots].sort();
|
|
106
|
+
const sortedProtectedPaths = [...protectedPaths].sort();
|
|
107
|
+
|
|
108
|
+
return {
|
|
109
|
+
ignoredRoots: sortedIgnoredRoots,
|
|
110
|
+
protectedRoots: sortedProtectedRoots,
|
|
111
|
+
protectedPaths: sortedProtectedPaths,
|
|
112
|
+
hash: stableHash(
|
|
113
|
+
JSON.stringify({
|
|
114
|
+
ignoredRoots: sortedIgnoredRoots,
|
|
115
|
+
protectedRoots: sortedProtectedRoots,
|
|
116
|
+
protectedPaths: sortedProtectedPaths,
|
|
117
|
+
}),
|
|
118
|
+
),
|
|
119
|
+
};
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
function isProtectedPath(
|
|
123
|
+
profile: StyleScopeProfile,
|
|
124
|
+
relativePath: string,
|
|
125
|
+
): boolean {
|
|
126
|
+
return profile.protectedPaths.some((path) => isWithinPath(relativePath, path)) ||
|
|
127
|
+
profile.protectedRoots.some((path) => isWithinPath(relativePath, path));
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
export function shouldIncludeStylePath(
|
|
131
|
+
profile: StyleScopeProfile,
|
|
132
|
+
path: string,
|
|
133
|
+
projectDir?: string,
|
|
134
|
+
): boolean {
|
|
135
|
+
const relativePath = normalizeRelativePath(toRelativeProjectPath(path, projectDir));
|
|
136
|
+
if (!relativePath) return true;
|
|
137
|
+
if (isProtectedPath(profile, relativePath)) return true;
|
|
138
|
+
|
|
139
|
+
return !profile.ignoredRoots.some((root) => isWithinPath(relativePath, root));
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export function shouldTraverseStyleDirectory(
|
|
143
|
+
profile: StyleScopeProfile,
|
|
144
|
+
directoryPath: string,
|
|
145
|
+
projectDir?: string,
|
|
146
|
+
): boolean {
|
|
147
|
+
const relativePath = normalizeRelativePath(toRelativeProjectPath(directoryPath, projectDir));
|
|
148
|
+
if (!relativePath) return true;
|
|
149
|
+
if (isProtectedPath(profile, relativePath)) return true;
|
|
150
|
+
|
|
151
|
+
const ignoredRoot = profile.ignoredRoots.find((root) => isWithinPath(relativePath, root));
|
|
152
|
+
if (!ignoredRoot) return true;
|
|
153
|
+
|
|
154
|
+
return profile.protectedRoots.some((root) => isWithinPath(root, relativePath)) ||
|
|
155
|
+
profile.protectedPaths.some((path) => isWithinPath(path, relativePath));
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
export function filterFilesForStyleScope<T extends { path: string }>(
|
|
159
|
+
files: T[],
|
|
160
|
+
profile: StyleScopeProfile,
|
|
161
|
+
projectDir?: string,
|
|
162
|
+
): T[] {
|
|
163
|
+
return files.filter((file) => shouldIncludeStylePath(profile, file.path, projectDir));
|
|
164
|
+
}
|
|
@@ -1,19 +1,7 @@
|
|
|
1
1
|
import type { FSAdapter, FSAdapterConfig } from "./veryfront/types.js";
|
|
2
2
|
import { createError, toError } from "../../../errors/veryfront-error.js";
|
|
3
3
|
import { withSpan } from "../../../observability/tracing/otlp-setup.js";
|
|
4
|
-
import {
|
|
5
|
-
clearSSRModuleCache,
|
|
6
|
-
clearSSRModuleCacheForProject,
|
|
7
|
-
} from "../../../modules/react-loader/ssr-module-loader/cache/index.js";
|
|
8
|
-
import { clearRouterDetectionCacheForProject } from "../../../rendering/router-detection.js";
|
|
9
|
-
import {
|
|
10
|
-
clearModulePathCache,
|
|
11
|
-
invalidateModulePaths,
|
|
12
|
-
} from "../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
13
|
-
import { clearSnippetCacheForProject } from "../../../rendering/snippet-renderer.js";
|
|
14
|
-
import { clearRendererCacheForProject } from "../../../rendering/renderer.js";
|
|
15
|
-
import { invalidateProjectCSS } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
16
|
-
import { invalidateProjectCandidateManifests } from "../../../rendering/orchestrator/css-candidate-manifest.js";
|
|
4
|
+
import { createDefaultInvalidationCallbacks } from "./veryfront/default-invalidation-callbacks.js";
|
|
17
5
|
|
|
18
6
|
export function createFSAdapter(config: FSAdapterConfig): Promise<FSAdapter> {
|
|
19
7
|
const type = config.type ?? "local";
|
|
@@ -36,20 +24,7 @@ export function createFSAdapter(config: FSAdapterConfig): Promise<FSAdapter> {
|
|
|
36
24
|
if (type === "veryfront-api") {
|
|
37
25
|
const configWithCallbacks: FSAdapterConfig = {
|
|
38
26
|
...config,
|
|
39
|
-
invalidationCallbacks:
|
|
40
|
-
clearSSRModuleCache,
|
|
41
|
-
clearModulePathCache,
|
|
42
|
-
invalidateModulePaths,
|
|
43
|
-
clearSSRModuleCacheForProject,
|
|
44
|
-
clearRouterDetectionCacheForProject,
|
|
45
|
-
clearSnippetCacheForProject,
|
|
46
|
-
clearRendererCacheForProject,
|
|
47
|
-
clearProjectCSSCache: (projectSlug: string) => {
|
|
48
|
-
invalidateProjectCSS(projectSlug);
|
|
49
|
-
invalidateProjectCandidateManifests(projectSlug);
|
|
50
|
-
},
|
|
51
|
-
...config.invalidationCallbacks,
|
|
52
|
-
},
|
|
27
|
+
invalidationCallbacks: createDefaultInvalidationCallbacks(config.invalidationCallbacks),
|
|
53
28
|
};
|
|
54
29
|
|
|
55
30
|
if (proxyMode) {
|
|
@@ -245,6 +245,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
245
245
|
},
|
|
246
246
|
clearFileListIndex: () => this.readOps.clearFileListIndex(),
|
|
247
247
|
setFileListCache: (cacheKey, files) => this.cache.setAsync(cacheKey, files),
|
|
248
|
+
pregenerateStyles: (files) => this.triggerCSSPregeneration(files),
|
|
248
249
|
});
|
|
249
250
|
|
|
250
251
|
logger.debug("Created", {
|
|
@@ -349,12 +350,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
349
350
|
sourceFilesWithContent: fileSummary.sourceFilesWithContent,
|
|
350
351
|
});
|
|
351
352
|
|
|
352
|
-
// Trigger CSS pre-generation
|
|
353
|
-
// This
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
fileSummary.sourceFilesWithContent > 0
|
|
357
|
-
) {
|
|
353
|
+
// Trigger CSS pre-generation after the initial file snapshot is ready.
|
|
354
|
+
// This keeps stylesheet generation off the first styles request for both
|
|
355
|
+
// preview branches and published content.
|
|
356
|
+
if (fileSummary.sourceFilesWithContent > 0) {
|
|
358
357
|
this.triggerCSSPregeneration(files).catch(() => {
|
|
359
358
|
// Error already logged in triggerCSSPregeneration
|
|
360
359
|
});
|
|
@@ -445,6 +444,13 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
445
444
|
|
|
446
445
|
const files = await fetchFileListForContext(this.client, warmupContext);
|
|
447
446
|
await this.cache.setAsync(effectiveCacheKey, files);
|
|
447
|
+
const fileSummary = summarizeFileList(files);
|
|
448
|
+
|
|
449
|
+
if (fileSummary.sourceFilesWithContent > 0) {
|
|
450
|
+
this.triggerCSSPregeneration(files).catch(() => {
|
|
451
|
+
// Error already logged in triggerCSSPregeneration
|
|
452
|
+
});
|
|
453
|
+
}
|
|
448
454
|
|
|
449
455
|
logger.debug("File list warmup complete", {
|
|
450
456
|
reason,
|
|
@@ -716,34 +722,57 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
716
722
|
const { pregenerateCSSFromFiles, findStylesheetFromFiles } = await import(
|
|
717
723
|
"../../../../html/styles-builder/css-pregeneration.js"
|
|
718
724
|
);
|
|
725
|
+
const { resolveStyleContentVersion } = await import(
|
|
726
|
+
"../../../../html/styles-builder/content-version.js"
|
|
727
|
+
);
|
|
728
|
+
const { createStyleScopeProfile } = await import(
|
|
729
|
+
"../../../../html/styles-builder/style-scope-profile.js"
|
|
730
|
+
);
|
|
719
731
|
|
|
720
732
|
let stylesheetPath: string | undefined;
|
|
733
|
+
let styleProfile = createStyleScopeProfile();
|
|
721
734
|
const projectDir = this.normalizer.getProjectDir();
|
|
722
735
|
|
|
723
|
-
if (projectDir) {
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
}
|
|
732
|
-
|
|
733
|
-
|
|
734
|
-
|
|
735
|
-
|
|
736
|
-
|
|
736
|
+
if (!projectDir) {
|
|
737
|
+
logger.debug("Skipping CSS pre-generation without projectDir", {
|
|
738
|
+
projectSlug: this.projectSlug,
|
|
739
|
+
});
|
|
740
|
+
return;
|
|
741
|
+
}
|
|
742
|
+
|
|
743
|
+
try {
|
|
744
|
+
const { runtime } = await import("../../registry.js");
|
|
745
|
+
const { getConfig } = await import("../../../../config/index.js");
|
|
746
|
+
const adapter = await runtime.get();
|
|
747
|
+
const cacheKey = this.client.getProjectId() || this.projectSlug;
|
|
748
|
+
const config = await getConfig(projectDir, adapter, { cacheKey });
|
|
749
|
+
stylesheetPath = config?.tailwind?.stylesheet;
|
|
750
|
+
styleProfile = createStyleScopeProfile(config);
|
|
751
|
+
} catch (error) {
|
|
752
|
+
logger.debug("Failed to load config for CSS pre-generation", {
|
|
753
|
+
projectSlug: this.projectSlug,
|
|
754
|
+
error: error instanceof Error ? error.message : String(error),
|
|
755
|
+
});
|
|
737
756
|
}
|
|
738
757
|
|
|
739
758
|
const stylesheet = findStylesheetFromFiles(files, stylesheetPath);
|
|
759
|
+
const projectVersion = resolveStyleContentVersion(this.contentContext, {
|
|
760
|
+
branch: this.contentContext?.branch ?? null,
|
|
761
|
+
releaseId: this.contentContext?.releaseId ?? null,
|
|
762
|
+
environmentName: this.contentContext?.environmentName ?? null,
|
|
763
|
+
});
|
|
740
764
|
|
|
741
765
|
await pregenerateCSSFromFiles({
|
|
742
766
|
projectSlug: this.projectSlug,
|
|
767
|
+
projectVersion,
|
|
768
|
+
projectDir,
|
|
743
769
|
files,
|
|
770
|
+
styleProfile,
|
|
744
771
|
stylesheet,
|
|
745
772
|
stylesheetPath,
|
|
746
773
|
minify: true,
|
|
774
|
+
environment: "preview",
|
|
775
|
+
buildMode: "production",
|
|
747
776
|
});
|
|
748
777
|
} catch (error) {
|
|
749
778
|
logger.warn("CSS pre-generation failed", {
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import {
|
|
2
|
+
clearSSRModuleCache,
|
|
3
|
+
clearSSRModuleCacheForProject,
|
|
4
|
+
} from "../../../../modules/react-loader/ssr-module-loader/cache/index.js";
|
|
5
|
+
import { clearRouterDetectionCacheForProject } from "../../../../rendering/router-detection.js";
|
|
6
|
+
import {
|
|
7
|
+
clearModulePathCache,
|
|
8
|
+
invalidateModulePaths,
|
|
9
|
+
} from "../../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
10
|
+
import { clearSnippetCacheForProject } from "../../../../rendering/snippet-renderer.js";
|
|
11
|
+
import { clearRendererCacheForProject } from "../../../../rendering/renderer.js";
|
|
12
|
+
import { invalidateProjectCSS } from "../../../../html/styles-builder/tailwind-compiler.js";
|
|
13
|
+
import { invalidatePreparedProjectCSS } from "../../../../html/styles-builder/prepared-project-css-cache.js";
|
|
14
|
+
import { invalidateProjectCandidateManifests } from "../../../../rendering/orchestrator/css-candidate-manifest.js";
|
|
15
|
+
import type { InvalidationCallbacks } from "./types.js";
|
|
16
|
+
|
|
17
|
+
export function createDefaultInvalidationCallbacks(
|
|
18
|
+
callbacks?: InvalidationCallbacks,
|
|
19
|
+
): InvalidationCallbacks {
|
|
20
|
+
return {
|
|
21
|
+
clearSSRModuleCache,
|
|
22
|
+
clearModulePathCache,
|
|
23
|
+
invalidateModulePaths,
|
|
24
|
+
clearSSRModuleCacheForProject,
|
|
25
|
+
clearRouterDetectionCacheForProject,
|
|
26
|
+
clearSnippetCacheForProject,
|
|
27
|
+
clearRendererCacheForProject,
|
|
28
|
+
clearProjectCSSCache: (projectSlug: string) => {
|
|
29
|
+
invalidateProjectCSS(projectSlug);
|
|
30
|
+
invalidatePreparedProjectCSS(projectSlug);
|
|
31
|
+
invalidateProjectCandidateManifests(projectSlug);
|
|
32
|
+
},
|
|
33
|
+
...callbacks,
|
|
34
|
+
};
|
|
35
|
+
}
|
|
@@ -4,18 +4,8 @@ import { CACHE_INVARIANT_VIOLATION, INVALID_ARGUMENT } from "../../../../errors/
|
|
|
4
4
|
import { buildProxyManagerCacheKey } from "../../../../cache/index.js";
|
|
5
5
|
import { VeryfrontFSAdapter } from "./index.js";
|
|
6
6
|
import type { CacheStats, FSAdapterConfig, ResolvedContentContext } from "./types.js";
|
|
7
|
-
import {
|
|
8
|
-
clearSSRModuleCache,
|
|
9
|
-
clearSSRModuleCacheForProject,
|
|
10
|
-
} from "../../../../modules/react-loader/ssr-module-loader/cache/index.js";
|
|
11
|
-
import { clearRouterDetectionCacheForProject } from "../../../../rendering/router-detection.js";
|
|
12
|
-
import {
|
|
13
|
-
clearModulePathCache,
|
|
14
|
-
invalidateModulePaths,
|
|
15
|
-
} from "../../../../transforms/mdx/esm-module-loader/cache/index.js";
|
|
16
|
-
import { clearSnippetCacheForProject } from "../../../../rendering/snippet-renderer.js";
|
|
17
|
-
import { clearRendererCacheForProject } from "../../../../rendering/renderer.js";
|
|
18
7
|
import { GetAdapterParamsSchema } from "./schemas/index.js";
|
|
8
|
+
import { createDefaultInvalidationCallbacks } from "./default-invalidation-callbacks.js";
|
|
19
9
|
|
|
20
10
|
const logger = baseLogger.component("proxy-fs-adapter-manager");
|
|
21
11
|
|
|
@@ -306,16 +296,9 @@ export class ProxyFSAdapterManager {
|
|
|
306
296
|
projectId,
|
|
307
297
|
apiToken: effectiveToken,
|
|
308
298
|
},
|
|
309
|
-
invalidationCallbacks:
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
invalidateModulePaths,
|
|
313
|
-
clearSSRModuleCacheForProject,
|
|
314
|
-
clearRouterDetectionCacheForProject,
|
|
315
|
-
clearSnippetCacheForProject,
|
|
316
|
-
clearRendererCacheForProject,
|
|
317
|
-
...this.baseConfig.invalidationCallbacks,
|
|
318
|
-
},
|
|
299
|
+
invalidationCallbacks: createDefaultInvalidationCallbacks(
|
|
300
|
+
this.baseConfig.invalidationCallbacks,
|
|
301
|
+
),
|
|
319
302
|
};
|
|
320
303
|
|
|
321
304
|
const adapter = new VeryfrontFSAdapter(config);
|
|
@@ -43,6 +43,7 @@ interface WebSocketDeps {
|
|
|
43
43
|
cacheKey: string,
|
|
44
44
|
files: Array<{ path: string; content?: string }>,
|
|
45
45
|
) => Promise<void>;
|
|
46
|
+
pregenerateStyles?: (files: Array<{ path: string; content?: string }>) => Promise<void>;
|
|
46
47
|
}
|
|
47
48
|
|
|
48
49
|
export class WebSocketManager {
|
|
@@ -629,6 +630,7 @@ export class WebSocketManager {
|
|
|
629
630
|
const cacheKey = buildFileListCacheKey(contentContext);
|
|
630
631
|
await this.deps.setFileListCache(cacheKey, files);
|
|
631
632
|
this.deps.clearFileListIndex();
|
|
633
|
+
await this.deps.pregenerateStyles?.(files);
|
|
632
634
|
|
|
633
635
|
logger.debug("Fresh files cached (memory + Redis)", {
|
|
634
636
|
cacheKey,
|
|
@@ -771,6 +773,7 @@ export class WebSocketManager {
|
|
|
771
773
|
const cacheKey = buildFileListCacheKey(contentContext);
|
|
772
774
|
await this.deps.setFileListCache(cacheKey, files);
|
|
773
775
|
this.deps.clearFileListIndex();
|
|
776
|
+
await this.deps.pregenerateStyles?.(files);
|
|
774
777
|
|
|
775
778
|
logger.debug("FRESH FILES FETCHED", {
|
|
776
779
|
cacheKey,
|
|
@@ -3,7 +3,10 @@ import {
|
|
|
3
3
|
type FileDetail,
|
|
4
4
|
type FileListResult,
|
|
5
5
|
type ListFilesOptions,
|
|
6
|
+
type ProjectStyleArtifactResolution,
|
|
7
|
+
type ResolveStyleArtifactInput,
|
|
6
8
|
type TokenProvider,
|
|
9
|
+
type UpsertStyleArtifactInput,
|
|
7
10
|
VeryfrontAPIOperations,
|
|
8
11
|
} from "./operations.js";
|
|
9
12
|
import { API_CLIENT_ERROR, type VeryfrontAPIConfig } from "./types.js";
|
|
@@ -340,6 +343,20 @@ export class VeryfrontApiClient {
|
|
|
340
343
|
return this.operations.lookupProjectByDomain(domain);
|
|
341
344
|
}
|
|
342
345
|
|
|
346
|
+
resolveStyleArtifact(
|
|
347
|
+
input: ResolveStyleArtifactInput,
|
|
348
|
+
projectRef = this.getProjectSlug()!,
|
|
349
|
+
): Promise<ProjectStyleArtifactResolution> {
|
|
350
|
+
return this.operations.resolveStyleArtifact(projectRef, input);
|
|
351
|
+
}
|
|
352
|
+
|
|
353
|
+
upsertStyleArtifact(
|
|
354
|
+
input: UpsertStyleArtifactInput,
|
|
355
|
+
projectRef = this.getProjectSlug()!,
|
|
356
|
+
): Promise<ProjectStyleArtifactResolution> {
|
|
357
|
+
return this.operations.upsertStyleArtifact(projectRef, input);
|
|
358
|
+
}
|
|
359
|
+
|
|
343
360
|
// =============================================================================
|
|
344
361
|
// Adapter Convenience Methods
|
|
345
362
|
// =============================================================================
|