veryfront 0.1.53 → 0.1.55
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/deno.js +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler-cache.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler-cache.js +8 -4
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts +2 -1
- package/esm/src/html/styles-builder/tailwind-compiler.d.ts.map +1 -1
- package/esm/src/html/styles-builder/tailwind-compiler.js +7 -3
- package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/constants.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/constants.js +2 -2
- package/esm/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.d.ts.map +1 -1
- package/esm/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.js +3 -4
- package/esm/src/rendering/orchestrator/pipeline.d.ts +2 -0
- package/esm/src/rendering/orchestrator/pipeline.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/pipeline.js +33 -7
- package/esm/src/server/dev-server/error-overlay/html-template.js +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-css.handler.js +3 -1
- package/esm/src/server/handlers/preview/markdown-html-generator.d.ts +0 -4
- package/esm/src/server/handlers/preview/markdown-html-generator.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-html-generator.js +6 -25
- package/esm/src/server/handlers/preview/markdown-preview.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-preview.handler.js +0 -3
- package/esm/src/server/handlers/request/css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/css.handler.js +3 -3
- package/esm/src/studio/bridge/bridge-bundle.generated.d.ts.map +1 -1
- package/esm/src/studio/bridge/bridge-bundle.generated.js +1 -1
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/html/styles-builder/tailwind-compiler-cache.ts +8 -3
- package/src/src/html/styles-builder/tailwind-compiler.ts +11 -3
- package/src/src/modules/react-loader/ssr-module-loader/constants.ts +2 -2
- package/src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts +3 -4
- package/src/src/rendering/orchestrator/pipeline.ts +46 -8
- package/src/src/server/dev-server/error-overlay/html-template.ts +1 -1
- package/src/src/server/handlers/dev/styles-css.handler.ts +3 -1
- package/src/src/server/handlers/preview/markdown-html-generator.ts +5 -29
- package/src/src/server/handlers/preview/markdown-preview.handler.ts +0 -3
- package/src/src/server/handlers/request/css.handler.ts +11 -3
- package/src/src/studio/bridge/bridge-bundle.generated.ts +1 -1
package/esm/deno.js
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { compile } from "tailwindcss";
|
|
2
|
-
export declare function getCompiler(stylesheet: string): Promise<Awaited<ReturnType<typeof compile>>>;
|
|
2
|
+
export declare function getCompiler(stylesheet: string, projectSlug?: string): Promise<Awaited<ReturnType<typeof compile>>>;
|
|
3
3
|
export declare function invalidateCompiler(): void;
|
|
4
4
|
/**
|
|
5
5
|
* Get compiler cache statistics for monitoring.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailwind-compiler-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler-cache.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA2EtC,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,
|
|
1
|
+
{"version":3,"file":"tailwind-compiler-cache.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler-cache.ts"],"names":[],"mappings":"AAYA,OAAO,EAAE,OAAO,EAAE,MAAM,aAAa,CAAC;AA2EtC,wBAAsB,WAAW,CAC/B,UAAU,EAAE,MAAM,EAClB,WAAW,CAAC,EAAE,MAAM,GACnB,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,OAAO,OAAO,CAAC,CAAC,CAAC,CAkD9C;AAED,wBAAgB,kBAAkB,IAAI,IAAI,CAGzC;AAED;;GAEG;AACH,wBAAgB,qBAAqB,IAAI;IACvC,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,KAAK,CAAC;QAAE,IAAI,EAAE,MAAM,CAAC;QAAC,SAAS,EAAE,MAAM,CAAC;QAAC,WAAW,EAAE,MAAM,CAAA;KAAE,CAAC,CAAC;CAC1E,CAQA;AAED,wBAAgB,gBAAgB,CAAC,EAAE,CAAC,EAAE,MAAM,GAAG,IAAI,CAalD"}
|
|
@@ -62,14 +62,18 @@ function evictOldestCompiler() {
|
|
|
62
62
|
compilerCache.delete(oldestKey);
|
|
63
63
|
logger.debug("Evicted oldest compiler from cache", { hash: oldestKey });
|
|
64
64
|
}
|
|
65
|
-
export async function getCompiler(stylesheet) {
|
|
66
|
-
|
|
65
|
+
export async function getCompiler(stylesheet, projectSlug) {
|
|
66
|
+
// Tailwind v4's compile().build() is stateful — it accumulates candidates
|
|
67
|
+
// across calls. Without per-project isolation, projects sharing the same
|
|
68
|
+
// stylesheet on the shared pool contaminate each other's CSS output.
|
|
69
|
+
const stylesheetHash = hashString(stylesheet);
|
|
70
|
+
const hash = projectSlug ? `${projectSlug}:${stylesheetHash}` : stylesheetHash;
|
|
67
71
|
const cached = compilerCache.get(hash);
|
|
68
72
|
if (cached) {
|
|
69
|
-
logger.debug("Compiler cache hit", { hash });
|
|
73
|
+
logger.debug("Compiler cache hit", { hash, projectSlug });
|
|
70
74
|
return cached.compiler;
|
|
71
75
|
}
|
|
72
|
-
logger.debug("Creating new compiler", { hash });
|
|
76
|
+
logger.debug("Creating new compiler", { hash, projectSlug });
|
|
73
77
|
const tailwindBase = await getTailwindBaseCSS();
|
|
74
78
|
const pluginCache = new Map();
|
|
75
79
|
const pluginErrors = new Map();
|
|
@@ -11,6 +11,7 @@ export interface GenerateOptions {
|
|
|
11
11
|
minify?: boolean;
|
|
12
12
|
environment?: string;
|
|
13
13
|
buildMode?: "development" | "production";
|
|
14
|
+
projectSlug?: string;
|
|
14
15
|
}
|
|
15
16
|
export interface CSSErrorInfo {
|
|
16
17
|
title: string;
|
|
@@ -32,7 +33,7 @@ export declare function getProjectCSS(projectSlug: string, stylesheet: string |
|
|
|
32
33
|
* @param expectedHash - The CSS hash to regenerate
|
|
33
34
|
* @returns The regenerated CSS if inputs are cached and hash matches, undefined otherwise
|
|
34
35
|
*/
|
|
35
|
-
export declare function regenerateCSSByHash(expectedHash: string): Promise<string | undefined>;
|
|
36
|
+
export declare function regenerateCSSByHash(expectedHash: string, projectSlug: string | undefined): Promise<string | undefined>;
|
|
36
37
|
export declare function generateTailwindCSS(stylesheet: string | undefined, candidates: string[] | Set<string>, options?: GenerateOptions): Promise<TailwindResult>;
|
|
37
38
|
export declare function formatCSSError(error: Error | string): CSSErrorInfo;
|
|
38
39
|
//# sourceMappingURL=tailwind-compiler.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"tailwind-compiler.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAShC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"tailwind-compiler.d.ts","sourceRoot":"","sources":["../../../../src/src/html/styles-builder/tailwind-compiler.ts"],"names":[],"mappings":"AAwBA,OAAO,EAAE,iBAAiB,EAAE,0BAA0B,EAAE,OAAO,EAAE,MAAM,0BAA0B,CAAC;AAClG,OAAO,EAAE,mBAAmB,EAAE,MAAM,oBAAoB,CAAC;AACzD,OAAO,EACL,gBAAgB,EAChB,qBAAqB,EACrB,kBAAkB,GACnB,MAAM,8BAA8B,CAAC;AACtC,OAAO,EACL,aAAa,EACb,mBAAmB,EACnB,aAAa,EACb,YAAY,EACZ,iBAAiB,GAClB,MAAM,qBAAqB,CAAC;AAC7B,OAAO,EACL,yBAAyB,EACzB,oBAAoB,EACpB,yBAAyB,EACzB,4BAA4B,GAC7B,MAAM,wBAAwB,CAAC;AAShC,MAAM,WAAW,cAAc;IAC7B,GAAG,EAAE,MAAM,CAAC;IACZ,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,MAAM,WAAW,eAAe;IAC9B,MAAM,CAAC,EAAE,OAAO,CAAC;IACjB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,SAAS,CAAC,EAAE,aAAa,GAAG,YAAY,CAAC;IACzC,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED,MAAM,WAAW,YAAY;IAC3B,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,UAAU,EAAE,MAAM,CAAC;CACpB;AAMD,wBAAsB,aAAa,CACjC,WAAW,EAAE,MAAM,EACnB,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,GAAG,CAAC,MAAM,CAAC,EACvB,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC;IAAE,GAAG,EAAE,MAAM,CAAC;IAAC,IAAI,EAAE,MAAM,CAAC;IAAC,SAAS,EAAE,OAAO,CAAA;CAAE,CAAC,CAsE5D;AAMD;;;;;;;;;GASG;AACH,wBAAsB,mBAAmB,CACvC,YAAY,EAAE,MAAM,EACpB,WAAW,EAAE,MAAM,GAAG,SAAS,GAC9B,OAAO,CAAC,MAAM,GAAG,SAAS,CAAC,CA4D7B;AAMD,wBAAsB,mBAAmB,CACvC,UAAU,EAAE,MAAM,GAAG,SAAS,EAC9B,UAAU,EAAE,MAAM,EAAE,GAAG,GAAG,CAAC,MAAM,CAAC,EAClC,OAAO,CAAC,EAAE,eAAe,GACxB,OAAO,CAAC,cAAc,CAAC,CAgCzB;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,KAAK,GAAG,MAAM,GAAG,YAAY,CAGlE"}
|
|
@@ -45,7 +45,10 @@ export async function getProjectCSS(projectSlug, stylesheet, candidates, options
|
|
|
45
45
|
}
|
|
46
46
|
const generationPromise = (async () => {
|
|
47
47
|
// Generate fresh CSS
|
|
48
|
-
const result = await generateTailwindCSS(context.stylesheet, candidates,
|
|
48
|
+
const result = await generateTailwindCSS(context.stylesheet, candidates, {
|
|
49
|
+
...options,
|
|
50
|
+
projectSlug,
|
|
51
|
+
});
|
|
49
52
|
if (result.error) {
|
|
50
53
|
const formatted = formatCSSError(result.error);
|
|
51
54
|
logger.error("Project CSS generation failed", {
|
|
@@ -88,7 +91,7 @@ export async function getProjectCSS(projectSlug, stylesheet, candidates, options
|
|
|
88
91
|
* @param expectedHash - The CSS hash to regenerate
|
|
89
92
|
* @returns The regenerated CSS if inputs are cached and hash matches, undefined otherwise
|
|
90
93
|
*/
|
|
91
|
-
export async function regenerateCSSByHash(expectedHash) {
|
|
94
|
+
export async function regenerateCSSByHash(expectedHash, projectSlug) {
|
|
92
95
|
const inFlight = inFlightRegeneration.get(expectedHash);
|
|
93
96
|
if (inFlight)
|
|
94
97
|
return await inFlight;
|
|
@@ -100,6 +103,7 @@ export async function regenerateCSSByHash(expectedHash) {
|
|
|
100
103
|
}
|
|
101
104
|
const result = await generateTailwindCSS(inputs.stylesheet, inputs.candidates, {
|
|
102
105
|
minify: true,
|
|
106
|
+
projectSlug,
|
|
103
107
|
});
|
|
104
108
|
if (result.error) {
|
|
105
109
|
logger.warn("CSS regeneration failed", {
|
|
@@ -145,7 +149,7 @@ export async function generateTailwindCSS(stylesheet, candidates, options) {
|
|
|
145
149
|
return await withSpan(SpanNames.HTML_GENERATE_TAILWIND_CSS, async () => {
|
|
146
150
|
const css = stylesheet ?? DEFAULT_STYLESHEET;
|
|
147
151
|
try {
|
|
148
|
-
const comp = await getCompiler(css);
|
|
152
|
+
const comp = await getCompiler(css, options?.projectSlug);
|
|
149
153
|
let output = comp.build(candidateArray);
|
|
150
154
|
if (options?.minify)
|
|
151
155
|
output = minifyCSS(output);
|
|
@@ -6,7 +6,7 @@ export declare const REDIS_KEY_PREFIX = "veryfront:ssr-module:";
|
|
|
6
6
|
/** Get environment-aware Redis TTL for SSR modules */
|
|
7
7
|
export declare function getSSRModuleRedisTTL(isProduction: boolean): number;
|
|
8
8
|
export { DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC, DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC };
|
|
9
|
-
export declare const CIRCUIT_BREAKER_THRESHOLD =
|
|
9
|
+
export declare const CIRCUIT_BREAKER_THRESHOLD = 25;
|
|
10
10
|
export declare const CIRCUIT_BREAKER_RESET_MS: number;
|
|
11
11
|
export declare function getMaxConcurrentTransforms(): number;
|
|
12
12
|
export declare function getTransformPerProjectLimit(): number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sCAAsC,EACtC,yCAAyC,EAG1C,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,OAAO,EAAE,sCAAsC,EAAE,yCAAyC,EAAE,CAAC;AAE7F,eAAO,MAAM,yBAAyB,
|
|
1
|
+
{"version":3,"file":"constants.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/constants.ts"],"names":[],"mappings":"AAEA,OAAO,EACL,sCAAsC,EACtC,yCAAyC,EAG1C,MAAM,mCAAmC,CAAC;AAE3C,eAAO,MAAM,4BAA4B,OAAO,CAAC;AACjD,eAAO,MAAM,uBAAuB,QAAqB,CAAC;AAE1D,eAAO,MAAM,wBAAwB,MAAM,CAAC;AAE5C,eAAO,MAAM,gBAAgB,0BAA0B,CAAC;AAExD,sDAAsD;AACtD,wBAAgB,oBAAoB,CAAC,YAAY,EAAE,OAAO,GAAG,MAAM,CAElE;AAED,OAAO,EAAE,sCAAsC,EAAE,yCAAyC,EAAE,CAAC;AAE7F,eAAO,MAAM,yBAAyB,KAAK,CAAC;AAC5C,eAAO,MAAM,wBAAwB,QAAW,CAAC;AAIjD,wBAAgB,0BAA0B,IAAI,MAAM,CAOnD;AASD,wBAAgB,2BAA2B,IAAI,MAAM,CAQpD;AAED,oEAAoE;AACpE,wBAAgB,0BAA0B,IAAI,IAAI,CAGjD;AAED,eAAO,MAAM,4BAA4B,MAAM,CAAC;AAChD,eAAO,MAAM,2BAA2B,QAAS,CAAC;AAElD,eAAO,MAAM,mBAAmB,KAAK,CAAC;AACtC,eAAO,MAAM,oBAAoB,KAAK,CAAC"}
|
|
@@ -10,8 +10,8 @@ export function getSSRModuleRedisTTL(isProduction) {
|
|
|
10
10
|
return getDistributedCacheTTL("ssr-module", isProduction);
|
|
11
11
|
}
|
|
12
12
|
export { DISTRIBUTED_SSR_MODULE_TTL_PREVIEW_SEC, DISTRIBUTED_SSR_MODULE_TTL_PRODUCTION_SEC };
|
|
13
|
-
export const CIRCUIT_BREAKER_THRESHOLD =
|
|
14
|
-
export const CIRCUIT_BREAKER_RESET_MS =
|
|
13
|
+
export const CIRCUIT_BREAKER_THRESHOLD = 25;
|
|
14
|
+
export const CIRCUIT_BREAKER_RESET_MS = 5 * 1000;
|
|
15
15
|
// Max concurrent ESM transforms (safety net, not throttle). Set to 0 to disable.
|
|
16
16
|
let _maxConcurrentTransforms;
|
|
17
17
|
export function getMaxConcurrentTransforms() {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"ssr-circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;
|
|
1
|
+
{"version":3,"file":"ssr-circuit-breaker.d.ts","sourceRoot":"","sources":["../../../../../src/src/modules/react-loader/ssr-module-loader/ssr-circuit-breaker.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAMH;;;;;;GAMG;AACH,qBAAa,iBAAiB;IAC5B;;;;OAIG;IACH,KAAK,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI;IA8BjD;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;IAIvC;;OAEG;IACH,aAAa,CAAC,UAAU,EAAE,MAAM,GAAG,IAAI;CAOxC"}
|
|
@@ -31,12 +31,11 @@ export class SSRCircuitBreaker {
|
|
|
31
31
|
if (failureRecord.count >= CIRCUIT_BREAKER_THRESHOLD &&
|
|
32
32
|
timeSinceFailure < CIRCUIT_BREAKER_RESET_MS) {
|
|
33
33
|
throw toError(createError({
|
|
34
|
-
type: "
|
|
34
|
+
type: "render",
|
|
35
35
|
message: `Component ${filePath} is temporarily blocked due to repeated failures. Will retry in ${Math.ceil((CIRCUIT_BREAKER_RESET_MS - timeSinceFailure) / 1000)}s.`,
|
|
36
36
|
context: {
|
|
37
|
-
|
|
38
|
-
phase: "
|
|
39
|
-
failures: failureRecord.count,
|
|
37
|
+
component: filePath,
|
|
38
|
+
phase: "server",
|
|
40
39
|
},
|
|
41
40
|
}));
|
|
42
41
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;
|
|
1
|
+
{"version":3,"file":"pipeline.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/pipeline.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAgBH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,iCAAiC,CAAC;AAEtE,OAAO,KAAK,EAAE,iBAAiB,EAAE,MAAM,+BAA+B,CAAC;AACvE,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,qBAAqB,CAAC;AACxD,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAC;AAChE,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,aAAa,CAAC;AACtD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,uBAAuB,CAAC;AAC7D,OAAO,KAAK,EAAE,gBAAgB,EAAE,aAAa,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AA6ChF,OAAO,EAAE,wBAAwB,EAAE,MAAM,gBAAgB,CAAC;AAE1D;;;;GAIG;AACH,MAAM,WAAW,wBAAwB;IACvC,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,iBAAiB,CAAC,CAAC;IACxE,aAAa,CAAC,MAAM,EAAE,YAAY,EAAE,IAAI,EAAE,MAAM,EAAE,QAAQ,CAAC,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACrF;AAED,MAAM,WAAW,oBAAoB;IACnC,YAAY,EAAE,YAAY,CAAC;IAC3B,gBAAgB,EAAE,wBAAwB,CAAC;IAC3C,YAAY,EAAE,YAAY,CAAC;IAC3B,kBAAkB,EAAE,kBAAkB,CAAC;IACvC,eAAe,EAAE,eAAe,CAAC;IACjC,OAAO,EAAE,cAAc,CAAC;IACxB,IAAI,EAAE,aAAa,GAAG,YAAY,CAAC;IACnC,UAAU,EAAE,MAAM,CAAC;IACnB,8EAA8E;IAC9E,iBAAiB,CAAC,EAAE,OAAO,qBAAqB,EAAE,sBAAsB,CAAC;CAC1E;AAQD,qBAAa,cAAc;IACzB,OAAO,CAAC,MAAM,CAAuB;IACrC,OAAO,CAAC,WAAW,CAAc;IACjC,OAAO,CAAC,kBAAkB,CAAqB;gBAEnC,MAAM,EAAE,oBAAoB;IAaxC;;;OAGG;IACH,gBAAgB,IAAI,IAAI;IAKxB,OAAO,CAAC,UAAU;IAIlB,OAAO,CAAC,sBAAsB;YAIhB,0BAA0B;IAaxC;;;;;;;;;OASG;YACW,qBAAqB;IAyDnC;;;OAGG;YACW,mBAAmB;IAqH3B,UAAU,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,YAAY,CAAC;IAgP9E,+EAA+E;IACzE,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,CAAC,EAAE,aAAa,GAAG,OAAO,CAAC,gBAAgB,CAAC;IA6LvF;;;;;;OAMG;IACH,OAAO,CAAC,aAAa;CAetB"}
|
|
@@ -30,6 +30,7 @@ import { setupSSRGlobals } from "../ssr-globals.js";
|
|
|
30
30
|
import { LAYOUT_EXTENSIONS } from "../layouts/types.js";
|
|
31
31
|
import { withTimeout, withTimeoutThrow } from "../utils/stream-utils.js";
|
|
32
32
|
import { extractCandidates, generateTailwindCSS } from "../../html/styles-builder/index.js";
|
|
33
|
+
import { getCSSByHashAsync, regenerateCSSByHash, } from "../../html/styles-builder/tailwind-compiler.js";
|
|
33
34
|
import { createEsmCache, createModuleCache, loadModule } from "./module-loader/index.js";
|
|
34
35
|
import { getCSSImports, runWithCSSCollector, } from "../../modules/react-loader/css-import-collector.js";
|
|
35
36
|
// Extracted modules
|
|
@@ -39,6 +40,7 @@ import { collectModulesToLoad, DATA_FETCH_TIMEOUT_MS, hasDataFetchingFunction, M
|
|
|
39
40
|
const renderPageLog = logger.component("render-page");
|
|
40
41
|
const renderPipelineLog = logger.component("render-pipeline");
|
|
41
42
|
const resolvePageDataLog = logger.component("resolve-page-data");
|
|
43
|
+
const RENDERED_CSS_HASH_RE = /href="\/_vf\/css\/([a-z0-9-]{1,16})\.css"/i;
|
|
42
44
|
// Re-export test helper for backward compatibility
|
|
43
45
|
export { __injectCssCacheForTests } from "./css-cache.js";
|
|
44
46
|
export class RenderPipeline {
|
|
@@ -68,6 +70,18 @@ export class RenderPipeline {
|
|
|
68
70
|
loadModule(filePath) {
|
|
69
71
|
return loadModule(filePath, this.moduleLoaderConfig);
|
|
70
72
|
}
|
|
73
|
+
extractRenderedCssHash(html) {
|
|
74
|
+
return html.match(RENDERED_CSS_HASH_RE)?.[1];
|
|
75
|
+
}
|
|
76
|
+
async resolveCssFromRenderedHtml(html, projectSlug) {
|
|
77
|
+
const cssHash = this.extractRenderedCssHash(html);
|
|
78
|
+
if (!cssHash)
|
|
79
|
+
return undefined;
|
|
80
|
+
const cachedCss = await getCSSByHashAsync(cssHash);
|
|
81
|
+
if (cachedCss)
|
|
82
|
+
return cachedCss;
|
|
83
|
+
return await regenerateCSSByHash(cssHash, projectSlug);
|
|
84
|
+
}
|
|
71
85
|
/**
|
|
72
86
|
* Load modules in parallel and return only successfully loaded ones.
|
|
73
87
|
*
|
|
@@ -438,15 +452,27 @@ export class RenderPipeline {
|
|
|
438
452
|
skipCachePersist: true,
|
|
439
453
|
}), CSS_SSR_TIMEOUT_MS, `CSS SSR for ${slug}`);
|
|
440
454
|
if (renderResult?.html) {
|
|
441
|
-
|
|
442
|
-
|
|
455
|
+
css = await this.resolveCssFromRenderedHtml(renderResult.html, options?.projectSlug ?? options?.projectId);
|
|
456
|
+
if (css) {
|
|
457
|
+
resolvePageDataLog.debug("Reused SSR CSS for page data", {
|
|
458
|
+
slug,
|
|
459
|
+
cssLength: css.length,
|
|
460
|
+
source: "rendered-html-hash",
|
|
461
|
+
});
|
|
462
|
+
}
|
|
463
|
+
else {
|
|
464
|
+
const candidates = extractCandidates(renderResult.html);
|
|
465
|
+
css = (await generateTailwindCSS(undefined, candidates, {
|
|
466
|
+
projectSlug: options?.projectSlug,
|
|
467
|
+
})).css;
|
|
468
|
+
resolvePageDataLog.debug("Fell back to HTML candidate CSS generation", {
|
|
469
|
+
slug,
|
|
470
|
+
htmlLength: renderResult.html.length,
|
|
471
|
+
cssLength: css?.length || 0,
|
|
472
|
+
});
|
|
473
|
+
}
|
|
443
474
|
if (css)
|
|
444
475
|
cachePageCss(cssCacheKey, css);
|
|
445
|
-
resolvePageDataLog.debug("Generated and cached CSS", {
|
|
446
|
-
slug,
|
|
447
|
-
htmlLength: renderResult.html.length,
|
|
448
|
-
cssLength: css?.length || 0,
|
|
449
|
-
});
|
|
450
476
|
}
|
|
451
477
|
}
|
|
452
478
|
catch (error) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles-css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-css.handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAanG,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"styles-css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-css.handler.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAanG,qBAAa,gBAAiB,SAAQ,WAAW;IAC/C,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;YAkGjE,cAAc;CAiB7B"}
|
|
@@ -39,7 +39,9 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
39
39
|
});
|
|
40
40
|
candidates = new Set();
|
|
41
41
|
}
|
|
42
|
-
const result = await generateTailwindCSS(rawCss, candidates
|
|
42
|
+
const result = await generateTailwindCSS(rawCss, candidates, {
|
|
43
|
+
projectSlug: ctx.projectSlug,
|
|
44
|
+
});
|
|
43
45
|
if (result.error) {
|
|
44
46
|
const formatted = formatCSSError(result.error);
|
|
45
47
|
logger.error("Tailwind error", {
|
|
@@ -24,10 +24,6 @@ interface MarkdownHtmlOptions {
|
|
|
24
24
|
projectId: string;
|
|
25
25
|
/** File path of the markdown file. */
|
|
26
26
|
filePath: string;
|
|
27
|
-
/** Branch ID for Yjs room GUID computation. */
|
|
28
|
-
branchId?: string | null;
|
|
29
|
-
/** API base URL for computing the WebSocket URL (e.g. "https://api.veryfront.com"). */
|
|
30
|
-
apiBaseUrl?: string;
|
|
31
27
|
}
|
|
32
28
|
/**
|
|
33
29
|
* Generate a complete HTML document for markdown preview rendering.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAKrD,oDAAoD;AACpD,UAAU,mBAAmB;IAC3B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,gDAAgD;IAChD,GAAG,EAAE,GAAG,CAAC;IACT,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;
|
|
1
|
+
{"version":3,"file":"markdown-html-generator.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-html-generator.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAKrD,oDAAoD;AACpD,UAAU,mBAAmB;IAC3B,wDAAwD;IACxD,OAAO,EAAE,MAAM,CAAC;IAChB,kDAAkD;IAClD,KAAK,EAAE,MAAM,CAAC;IACd,yCAAyC;IACzC,WAAW,EAAE,MAAM,CAAC;IACpB,iDAAiD;IACjD,OAAO,EAAE,OAAO,CAAC,OAAO,CAAC;IACzB,gDAAgD;IAChD,GAAG,EAAE,GAAG,CAAC;IACT,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;CAClB;AAuDD;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,mBAAmB,GAAG,MAAM,CA0FzE"}
|
|
@@ -21,42 +21,23 @@ function detectTheme(req, url) {
|
|
|
21
21
|
}
|
|
22
22
|
/**
|
|
23
23
|
* Generate the studio bridge `<script>` tag.
|
|
24
|
-
* Injected when embedded in Studio (`studio_embed=true`)
|
|
25
|
-
* markdown/MDX pages so the edit button and editor features are available.
|
|
24
|
+
* Injected only when embedded in Studio (`studio_embed=true`).
|
|
26
25
|
*/
|
|
27
|
-
function buildStudioScript(url, projectId, filePath
|
|
26
|
+
function buildStudioScript(url, projectId, filePath) {
|
|
28
27
|
const studioEmbed = url.searchParams.get("studio_embed") === "true";
|
|
29
|
-
|
|
30
|
-
if (!studioEmbed && !isMarkdown)
|
|
28
|
+
if (!studioEmbed)
|
|
31
29
|
return "";
|
|
32
30
|
const rawQueryProjectId = url.searchParams.get("vf_project_id")?.trim() || "";
|
|
33
|
-
// Validate query param
|
|
31
|
+
// Validate query param before using it in bridge config.
|
|
34
32
|
const queryProjectId = /^[a-zA-Z0-9_-]+$/.test(rawQueryProjectId) ? rawQueryProjectId : "";
|
|
35
33
|
const queryFileId = url.searchParams.get("vf_file_id")?.trim() || "";
|
|
36
34
|
const canonicalProjectId = queryProjectId || projectId;
|
|
37
35
|
const canonicalPageId = queryFileId || filePath;
|
|
38
|
-
// Compute Yjs WebSocket URL from the API base URL (Yjs endpoint lives on the API server)
|
|
39
|
-
let wsUrl = "";
|
|
40
|
-
if (apiBaseUrl) {
|
|
41
|
-
try {
|
|
42
|
-
const apiUrl = new URL(apiBaseUrl);
|
|
43
|
-
const wsProtocol = apiUrl.protocol === "https:" ? "wss:" : "ws:";
|
|
44
|
-
wsUrl = `${wsProtocol}//${apiUrl.host}/ws/${canonicalProjectId}/yjs`;
|
|
45
|
-
}
|
|
46
|
-
catch (_) {
|
|
47
|
-
/* expected: invalid API URL — wsUrl stays empty, bridge won't self-connect */
|
|
48
|
-
}
|
|
49
|
-
}
|
|
50
|
-
const yjsGuid = branchId ? `${canonicalProjectId}:${branchId}` : canonicalProjectId;
|
|
51
36
|
const bridgeConfig = {
|
|
52
37
|
projectId: canonicalProjectId,
|
|
53
38
|
pageId: canonicalPageId,
|
|
54
39
|
pagePath: filePath,
|
|
55
40
|
};
|
|
56
|
-
if (wsUrl)
|
|
57
|
-
bridgeConfig.wsUrl = wsUrl;
|
|
58
|
-
if (yjsGuid)
|
|
59
|
-
bridgeConfig.yjsGuid = yjsGuid;
|
|
60
41
|
// Escape </script> sequences to prevent XSS breakout from inline JSON
|
|
61
42
|
const safeJson = JSON.stringify(bridgeConfig).replace(/</g, "\\u003c");
|
|
62
43
|
return `<script>window.__VF_BRIDGE_CONFIG__=${safeJson};</script>
|
|
@@ -70,9 +51,9 @@ function buildStudioScript(url, projectId, filePath, branchId, apiBaseUrl) {
|
|
|
70
51
|
* studio bridge integration.
|
|
71
52
|
*/
|
|
72
53
|
export function generateMarkdownHtml(options) {
|
|
73
|
-
const { rawHtml, title, description, request, url, projectId, filePath
|
|
54
|
+
const { rawHtml, title, description, request, url, projectId, filePath } = options;
|
|
74
55
|
const theme = detectTheme(request, url);
|
|
75
|
-
const studioScript = buildStudioScript(url, projectId, filePath
|
|
56
|
+
const studioScript = buildStudioScript(url, projectId, filePath);
|
|
76
57
|
const themeAttrs = theme ? ` data-theme="${theme}" style="color-scheme: ${theme};"` : "";
|
|
77
58
|
return `<!DOCTYPE html>
|
|
78
59
|
<html lang="en"${themeAttrs}>
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"markdown-preview.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-preview.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"markdown-preview.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/preview/markdown-preview.handler.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AACH,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AAGrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AAgBnG,qBAAa,sBAAuB,SAAQ,WAAW;IACrD,QAAQ,EAAE,eAAe,CAKvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;YAiEjE,cAAc;CAiF7B"}
|
|
@@ -7,7 +7,6 @@ import { isExtendedFSAdapter } from "../../../platform/adapters/fs/wrapper.js";
|
|
|
7
7
|
import { getEnv } from "../../../platform/compat/process.js";
|
|
8
8
|
import { tryNotFoundFallback } from "../request/ssr/not-found-fallback.js";
|
|
9
9
|
import { generateMarkdownHtml } from "./markdown-html-generator.js";
|
|
10
|
-
import { getEnvironmentConfig } from "../../../config/environment-config.js";
|
|
11
10
|
import { validatePathSync } from "../../../security/index.js";
|
|
12
11
|
const logger = serverLogger.component("markdown-preview-handler");
|
|
13
12
|
// Priority 900: between MEDIUM (600) and LOW/SSR (1000)
|
|
@@ -110,8 +109,6 @@ export class MarkdownPreviewHandler extends BaseHandler {
|
|
|
110
109
|
url,
|
|
111
110
|
projectId: ctx.projectSlug || ctx.projectId || "markdown-preview",
|
|
112
111
|
filePath,
|
|
113
|
-
branchId: ctx.parsedDomain?.branch ?? null,
|
|
114
|
-
apiBaseUrl: getEnvironmentConfig().publicApiBaseUrl,
|
|
115
112
|
});
|
|
116
113
|
const responseBuilder = this.createResponseBuilder(ctx)
|
|
117
114
|
.withCache("no-cache")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/css.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;
|
|
1
|
+
{"version":3,"file":"css.handler.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/request/css.handler.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,OAAO,MAAM,2BAA2B,CAAC;AACrD,OAAO,EAAE,WAAW,EAAE,MAAM,qBAAqB,CAAC;AAClD,OAAO,KAAK,EAAE,cAAc,EAAE,eAAe,EAAmB,aAAa,EAAE,MAAM,aAAa,CAAC;AA0BnG,qBAAa,UAAW,SAAQ,WAAW;IACzC,QAAQ,EAAE,eAAe,CAOvB;IAEI,MAAM,CAAC,GAAG,EAAE,OAAO,CAAC,OAAO,EAAE,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,aAAa,CAAC;CAkEhF"}
|
|
@@ -6,11 +6,11 @@ import { getEnv } from "../../../platform/compat/process.js";
|
|
|
6
6
|
import { runWithRequestContext } from "../../../platform/adapters/fs/veryfront/multi-project-adapter.js";
|
|
7
7
|
/** Pattern to match hashed CSS URLs: /_vf/css/[8-char-hash].css */
|
|
8
8
|
const CSS_URL_PATTERN = /^\/_vf\/css\/([a-z0-9-]{1,16})\.css$/;
|
|
9
|
-
async function getCSSWithJITFallback(cssHash) {
|
|
9
|
+
async function getCSSWithJITFallback(cssHash, projectSlug) {
|
|
10
10
|
const cached = await getCSSByHashAsync(cssHash);
|
|
11
11
|
if (cached)
|
|
12
12
|
return cached;
|
|
13
|
-
return regenerateCSSByHash(cssHash);
|
|
13
|
+
return regenerateCSSByHash(cssHash, projectSlug);
|
|
14
14
|
}
|
|
15
15
|
export class CSSHandler extends BaseHandler {
|
|
16
16
|
metadata = {
|
|
@@ -35,7 +35,7 @@ export class CSSHandler extends BaseHandler {
|
|
|
35
35
|
// null — causing cross-pod cache misses. Wrap the lookup in request context
|
|
36
36
|
// so the API backend can resolve the token and project.
|
|
37
37
|
const effectiveToken = ctx.proxyToken || getEnv("VERYFRONT_API_TOKEN") || "";
|
|
38
|
-
const lookup = () => runWithCacheKeyContext(cacheCtx, () => getCSSWithJITFallback(cssHash));
|
|
38
|
+
const lookup = () => runWithCacheKeyContext(cacheCtx, () => getCSSWithJITFallback(cssHash, ctx.projectSlug ?? ctx.projectId));
|
|
39
39
|
const css = ctx.projectSlug
|
|
40
40
|
? await runWithRequestContext({
|
|
41
41
|
projectSlug: ctx.projectSlug,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"bridge-bundle.generated.d.ts","sourceRoot":"","sources":["../../../../src/src/studio/bridge/bridge-bundle.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB,EAAE,
|
|
1
|
+
{"version":3,"file":"bridge-bundle.generated.d.ts","sourceRoot":"","sources":["../../../../src/src/studio/bridge/bridge-bundle.generated.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,eAAO,MAAM,oBAAoB,EAAE,MAA67nD,CAAC"}
|