veryfront 0.1.84 → 0.1.86
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/platform/adapters/fs/veryfront/adapter.d.ts +4 -0
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +76 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts +11 -0
- package/esm/src/rendering/orchestrator/css-candidate-manifest.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/css-candidate-manifest.js +23 -12
- package/esm/src/server/handlers/dev/styles-candidate-scanner.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/styles-candidate-scanner.js +25 -8
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +1 -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 +15 -5
- package/package.json +1 -1
- package/src/deno.js +1 -1
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +87 -0
- package/src/src/rendering/orchestrator/css-candidate-manifest.ts +40 -14
- package/src/src/server/handlers/dev/styles-candidate-scanner.ts +37 -11
- package/src/src/server/handlers/dev/styles-css.handler.ts +25 -4
package/esm/deno.js
CHANGED
|
@@ -12,6 +12,9 @@ export declare class VeryfrontFSAdapter implements FSAdapter {
|
|
|
12
12
|
private initialized;
|
|
13
13
|
/** Resolves when file list initialization is complete (for coordinating reads) */
|
|
14
14
|
private fileListReadyResolve;
|
|
15
|
+
/** Single-flight background rewarm when the file list cache disappears */
|
|
16
|
+
private fileListWarmupPromise;
|
|
17
|
+
private fileListWarmupKey;
|
|
15
18
|
private projectData?;
|
|
16
19
|
private apiBaseUrl;
|
|
17
20
|
private apiToken;
|
|
@@ -29,6 +32,7 @@ export declare class VeryfrontFSAdapter implements FSAdapter {
|
|
|
29
32
|
constructor(config: FSAdapterConfig);
|
|
30
33
|
initialize(): Promise<void>;
|
|
31
34
|
private isPersistentCacheInvalidated;
|
|
35
|
+
private scheduleFileListWarmup;
|
|
32
36
|
getPokeMetrics(): {
|
|
33
37
|
received: number;
|
|
34
38
|
invalidationsTriggered: number;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/adapter.ts"],"names":[],"mappings":"AAEA,OAAO,KAAK,EACV,UAAU,EAEV,cAAc,EACd,SAAS,EACT,eAAe,EAEf,sBAAsB,EACvB,MAAM,YAAY,CAAC;AACpB,OAAO,KAAK,EAAE,QAAQ,EAAE,kBAAkB,EAAE,MAAM,eAAe,CAAC;AAClE,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,qCAAqC,CAAC;AA2BnE,qBAAa,kBAAmB,YAAW,SAAS;IAClD,OAAO,CAAC,MAAM,CAAqB;IACnC,OAAO,CAAC,KAAK,CAAY;IACzB,OAAO,CAAC,UAAU,CAAiB;IACnC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,MAAM,CAAsB;IACpC,OAAO,CAAC,OAAO,CAAiB;IAChC,OAAO,CAAC,WAAW,CAAS;IAE5B,kFAAkF;IAClF,OAAO,CAAC,oBAAoB,CAA6B;
|
|
1
|
+
{"version":3,"file":"adapter.d.ts","sourceRoot":"","sources":["../../../../../../src/src/platform/adapters/fs/veryfront/adapter.ts"],"names":[],"mappings":"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"}
|
|
@@ -27,6 +27,9 @@ export class VeryfrontFSAdapter {
|
|
|
27
27
|
initialized = false;
|
|
28
28
|
/** Resolves when file list initialization is complete (for coordinating reads) */
|
|
29
29
|
fileListReadyResolve = null;
|
|
30
|
+
/** Single-flight background rewarm when the file list cache disappears */
|
|
31
|
+
fileListWarmupPromise = null;
|
|
32
|
+
fileListWarmupKey = null;
|
|
30
33
|
projectData;
|
|
31
34
|
apiBaseUrl;
|
|
32
35
|
apiToken;
|
|
@@ -94,6 +97,9 @@ export class VeryfrontFSAdapter {
|
|
|
94
97
|
hasResult: !!result,
|
|
95
98
|
resultSize: result?.length ?? 0,
|
|
96
99
|
});
|
|
100
|
+
if (!result?.length) {
|
|
101
|
+
this.scheduleFileListWarmup("getFileList miss", cacheKey);
|
|
102
|
+
}
|
|
97
103
|
return result;
|
|
98
104
|
},
|
|
99
105
|
hasCachedFileList: async () => {
|
|
@@ -109,6 +115,9 @@ export class VeryfrontFSAdapter {
|
|
|
109
115
|
hasResult,
|
|
110
116
|
resultSize: result?.length ?? 0,
|
|
111
117
|
});
|
|
118
|
+
if (!hasResult) {
|
|
119
|
+
this.scheduleFileListWarmup("hasCachedFileList miss", cacheKey);
|
|
120
|
+
}
|
|
112
121
|
return hasResult;
|
|
113
122
|
},
|
|
114
123
|
isPersistentCacheInvalidated: (prefix) => this.isPersistentCacheInvalidated(prefix),
|
|
@@ -132,6 +141,9 @@ export class VeryfrontFSAdapter {
|
|
|
132
141
|
resultSize: result?.length ?? 0,
|
|
133
142
|
hasContent: result?.filter((f) => f.content)?.length ?? 0,
|
|
134
143
|
});
|
|
144
|
+
if (!result?.length) {
|
|
145
|
+
this.scheduleFileListWarmup("getFileListCache miss", cacheKey);
|
|
146
|
+
}
|
|
135
147
|
return result;
|
|
136
148
|
});
|
|
137
149
|
this.dirOps = new DirectoryOperations(this.client, this.cache, this.normalizer, contentContextGetter);
|
|
@@ -280,6 +292,65 @@ export class VeryfrontFSAdapter {
|
|
|
280
292
|
isPersistentCacheInvalidated(prefix) {
|
|
281
293
|
return isPrefixBeingInvalidated(prefix);
|
|
282
294
|
}
|
|
295
|
+
scheduleFileListWarmup(reason, cacheKey) {
|
|
296
|
+
if (!this.initialized || !this.contentContext)
|
|
297
|
+
return;
|
|
298
|
+
const effectiveCacheKey = cacheKey ?? buildFileListCacheKey(this.contentContext);
|
|
299
|
+
if (this.fileListWarmupPromise && this.fileListWarmupKey === effectiveCacheKey) {
|
|
300
|
+
logger.debug("File list warmup already in progress", {
|
|
301
|
+
reason,
|
|
302
|
+
cacheKey: effectiveCacheKey,
|
|
303
|
+
});
|
|
304
|
+
return;
|
|
305
|
+
}
|
|
306
|
+
const warmupContext = this.contentContext;
|
|
307
|
+
let warmupPromise = null;
|
|
308
|
+
warmupPromise = (async () => {
|
|
309
|
+
try {
|
|
310
|
+
const existing = await this.cache.getAsync(effectiveCacheKey);
|
|
311
|
+
if (existing?.length) {
|
|
312
|
+
logger.debug("Skipping file list warmup because cache is already populated", {
|
|
313
|
+
reason,
|
|
314
|
+
cacheKey: effectiveCacheKey,
|
|
315
|
+
fileCount: existing.length,
|
|
316
|
+
});
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
logger.debug("Starting file list warmup", {
|
|
320
|
+
reason,
|
|
321
|
+
cacheKey: effectiveCacheKey,
|
|
322
|
+
sourceType: warmupContext.sourceType,
|
|
323
|
+
branch: warmupContext.branch,
|
|
324
|
+
environmentName: warmupContext.environmentName,
|
|
325
|
+
releaseId: warmupContext.releaseId,
|
|
326
|
+
});
|
|
327
|
+
const files = await fetchFileListForContext(this.client, warmupContext);
|
|
328
|
+
await this.cache.setAsync(effectiveCacheKey, files);
|
|
329
|
+
logger.debug("File list warmup complete", {
|
|
330
|
+
reason,
|
|
331
|
+
cacheKey: effectiveCacheKey,
|
|
332
|
+
totalFiles: files.length,
|
|
333
|
+
filesWithContent: files.filter((file) => file.content).length,
|
|
334
|
+
});
|
|
335
|
+
}
|
|
336
|
+
catch (error) {
|
|
337
|
+
logger.warn("File list warmup failed", {
|
|
338
|
+
reason,
|
|
339
|
+
cacheKey: effectiveCacheKey,
|
|
340
|
+
error: error instanceof Error ? error.message : String(error),
|
|
341
|
+
});
|
|
342
|
+
}
|
|
343
|
+
finally {
|
|
344
|
+
if (warmupPromise && this.fileListWarmupPromise === warmupPromise) {
|
|
345
|
+
this.fileListWarmupPromise = null;
|
|
346
|
+
this.fileListWarmupKey = null;
|
|
347
|
+
}
|
|
348
|
+
}
|
|
349
|
+
})();
|
|
350
|
+
this.fileListWarmupPromise = warmupPromise;
|
|
351
|
+
this.fileListWarmupKey = effectiveCacheKey;
|
|
352
|
+
this.readOps.setFileListReadyPromise(warmupPromise);
|
|
353
|
+
}
|
|
283
354
|
getPokeMetrics() {
|
|
284
355
|
return this.wsManager.getPokeMetrics();
|
|
285
356
|
}
|
|
@@ -317,6 +388,8 @@ export class VeryfrontFSAdapter {
|
|
|
317
388
|
this.statOps.clearIndex();
|
|
318
389
|
this.dirOps.clearTree();
|
|
319
390
|
this.initialized = false;
|
|
391
|
+
this.fileListWarmupPromise = null;
|
|
392
|
+
this.fileListWarmupKey = null;
|
|
320
393
|
logger.debug("Disposed");
|
|
321
394
|
}
|
|
322
395
|
getCacheStats() {
|
|
@@ -342,6 +415,7 @@ export class VeryfrontFSAdapter {
|
|
|
342
415
|
hasFiles: !!files,
|
|
343
416
|
fileCount: files?.length ?? 0,
|
|
344
417
|
});
|
|
418
|
+
this.scheduleFileListWarmup("getAllSourceFiles miss", cacheKey);
|
|
345
419
|
return [];
|
|
346
420
|
}
|
|
347
421
|
const fileSummary = summarizeFileList(files);
|
|
@@ -429,6 +503,8 @@ export class VeryfrontFSAdapter {
|
|
|
429
503
|
if (contextChanged) {
|
|
430
504
|
this.statOps.clearIndex();
|
|
431
505
|
this.dirOps.clearTree();
|
|
506
|
+
this.fileListWarmupPromise = null;
|
|
507
|
+
this.fileListWarmupKey = null;
|
|
432
508
|
logger.debug("Cleared index and dirTree due to context change", {
|
|
433
509
|
oldContext,
|
|
434
510
|
newContext: context,
|
|
@@ -11,10 +11,21 @@ interface RouteCandidateOptions {
|
|
|
11
11
|
files: SourceFileLike[];
|
|
12
12
|
developmentMode: boolean;
|
|
13
13
|
}
|
|
14
|
+
interface ProjectCandidateOptions {
|
|
15
|
+
projectScope: string;
|
|
16
|
+
projectVersion: string;
|
|
17
|
+
projectDir: string;
|
|
18
|
+
files: SourceFileLike[];
|
|
19
|
+
developmentMode: boolean;
|
|
20
|
+
}
|
|
14
21
|
/**
|
|
15
22
|
* Resolve route-scoped Tailwind candidates from a precomputed per-project manifest.
|
|
16
23
|
*/
|
|
17
24
|
export declare function getRouteCandidates(options: RouteCandidateOptions): Set<string>;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve full-project Tailwind candidates from a precomputed per-project manifest.
|
|
27
|
+
*/
|
|
28
|
+
export declare function getProjectCandidates(options: ProjectCandidateOptions): Set<string>;
|
|
18
29
|
/**
|
|
19
30
|
* Invalidate cached candidate manifests for one project scope (or all scopes).
|
|
20
31
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"css-candidate-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/css-candidate-manifest.ts"],"names":[],"mappings":"AAIA,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAQD,UAAU,qBAAqB;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;
|
|
1
|
+
{"version":3,"file":"css-candidate-manifest.d.ts","sourceRoot":"","sources":["../../../../src/src/rendering/orchestrator/css-candidate-manifest.ts"],"names":[],"mappings":"AAIA,UAAU,cAAc;IACtB,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAQD,UAAU,qBAAqB;IAC7B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,EAAE,CAAC;IACzB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;AAED,UAAU,uBAAuB;IAC/B,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;IACvB,UAAU,EAAE,MAAM,CAAC;IACnB,KAAK,EAAE,cAAc,EAAE,CAAC;IACxB,eAAe,EAAE,OAAO,CAAC;CAC1B;AA0GD;;GAEG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,qBAAqB,GAAG,GAAG,CAAC,MAAM,CAAC,CAkC9E;AAED;;GAEG;AACH,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,uBAAuB,GAAG,GAAG,CAAC,MAAM,CAAC,CAGlF;AAED;;GAEG;AACH,wBAAgB,mCAAmC,CAAC,YAAY,CAAC,EAAE,MAAM,GAAG,IAAI,CAc/E"}
|
|
@@ -58,6 +58,21 @@ function buildCandidateManifest(files, projectDir) {
|
|
|
58
58
|
}
|
|
59
59
|
return { fileCandidates, allCandidates, builtAt: Date.now() };
|
|
60
60
|
}
|
|
61
|
+
function getOrBuildManifest(options) {
|
|
62
|
+
const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
|
|
63
|
+
const existingManifest = manifestCache.get(manifestKey);
|
|
64
|
+
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
65
|
+
? buildCandidateManifest(options.files, options.projectDir)
|
|
66
|
+
: existingManifest;
|
|
67
|
+
if (manifest !== existingManifest) {
|
|
68
|
+
manifestCache.set(manifestKey, manifest);
|
|
69
|
+
for (const key of routeCandidateCache.keys()) {
|
|
70
|
+
if (key.startsWith(`${manifestKey}:`))
|
|
71
|
+
routeCandidateCache.delete(key);
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
return manifest;
|
|
75
|
+
}
|
|
61
76
|
function addCandidatesForPath(target, manifest, path, projectDir) {
|
|
62
77
|
const absolutePath = normalizePath(path);
|
|
63
78
|
const relativePath = toRelativeProjectPath(path, projectDir);
|
|
@@ -73,18 +88,7 @@ function addCandidatesForPath(target, manifest, path, projectDir) {
|
|
|
73
88
|
*/
|
|
74
89
|
export function getRouteCandidates(options) {
|
|
75
90
|
const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
|
|
76
|
-
const
|
|
77
|
-
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
78
|
-
? buildCandidateManifest(options.files, options.projectDir)
|
|
79
|
-
: existingManifest;
|
|
80
|
-
if (manifest !== existingManifest) {
|
|
81
|
-
manifestCache.set(manifestKey, manifest);
|
|
82
|
-
// Clear route subsets when project-level file manifest is rebuilt.
|
|
83
|
-
for (const key of routeCandidateCache.keys()) {
|
|
84
|
-
if (key.startsWith(`${manifestKey}:`))
|
|
85
|
-
routeCandidateCache.delete(key);
|
|
86
|
-
}
|
|
87
|
-
}
|
|
91
|
+
const manifest = getOrBuildManifest(options);
|
|
88
92
|
const routeCacheKey = `${manifestKey}:${options.routeKey}`;
|
|
89
93
|
const cachedRoute = routeCandidateCache.get(routeCacheKey);
|
|
90
94
|
if (cachedRoute)
|
|
@@ -112,6 +116,13 @@ export function getRouteCandidates(options) {
|
|
|
112
116
|
});
|
|
113
117
|
return new Set(routeCandidates);
|
|
114
118
|
}
|
|
119
|
+
/**
|
|
120
|
+
* Resolve full-project Tailwind candidates from a precomputed per-project manifest.
|
|
121
|
+
*/
|
|
122
|
+
export function getProjectCandidates(options) {
|
|
123
|
+
const manifest = getOrBuildManifest(options);
|
|
124
|
+
return new Set(manifest.allCandidates);
|
|
125
|
+
}
|
|
115
126
|
/**
|
|
116
127
|
* Invalidate cached candidate manifests for one project scope (or all scopes).
|
|
117
128
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"styles-candidate-scanner.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-candidate-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"styles-candidate-scanner.d.ts","sourceRoot":"","sources":["../../../../../src/src/server/handlers/dev/styles-candidate-scanner.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAQH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AA8BlD;;;;;;GAMG;AACH,wBAAsB,wBAAwB,CAAC,GAAG,EAAE,cAAc,GAAG,OAAO,CAAC,GAAG,CAAC,MAAM,CAAC,CAAC,CA0CxF"}
|
|
@@ -11,12 +11,26 @@ import { extractCandidates } from "../../../html/styles-builder/tailwind-compile
|
|
|
11
11
|
import { serverLogger } from "../../../utils/index.js";
|
|
12
12
|
import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
13
13
|
import { join } from "../../../platform/compat/path/index.js";
|
|
14
|
+
import { getProjectCandidates } from "../../../rendering/orchestrator/css-candidate-manifest.js";
|
|
14
15
|
import { FRAMEWORK_CANDIDATES } from "./framework-candidates.generated.js";
|
|
15
16
|
const logger = serverLogger.component("styles-candidate-scanner");
|
|
16
17
|
const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
|
|
17
18
|
const SKIP_DIRS = new Set(["node_modules", ".cache", ".git", "dist", "build", ".vscode"]);
|
|
18
19
|
/** De-duplicated set of framework candidates, computed once at import time. */
|
|
19
20
|
const frameworkCandidates = new Set(FRAMEWORK_CANDIDATES);
|
|
21
|
+
function resolveProjectVersion(ctx, contentContext) {
|
|
22
|
+
if (contentContext?.releaseId)
|
|
23
|
+
return `release:${contentContext.releaseId}`;
|
|
24
|
+
if (contentContext?.branch)
|
|
25
|
+
return `branch:${contentContext.branch}`;
|
|
26
|
+
if (contentContext?.environmentName)
|
|
27
|
+
return `environment:${contentContext.environmentName}`;
|
|
28
|
+
if (ctx.releaseId)
|
|
29
|
+
return `release:${ctx.releaseId}`;
|
|
30
|
+
if (ctx.parsedDomain?.branch)
|
|
31
|
+
return `branch:${ctx.parsedDomain.branch}`;
|
|
32
|
+
return "live";
|
|
33
|
+
}
|
|
20
34
|
/**
|
|
21
35
|
* Extract Tailwind CSS candidate class names from all project source files.
|
|
22
36
|
*
|
|
@@ -38,14 +52,17 @@ export async function extractProjectCandidates(ctx) {
|
|
|
38
52
|
}
|
|
39
53
|
const candidates = new Set(frameworkCandidates);
|
|
40
54
|
const files = await fsAdapter.getAllSourceFiles();
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
55
|
+
const contentContext = typeof fsAdapter.getContentContext === "function"
|
|
56
|
+
? fsAdapter.getContentContext()
|
|
57
|
+
: null;
|
|
58
|
+
for (const cls of getProjectCandidates({
|
|
59
|
+
projectScope: ctx.projectSlug ?? contentContext?.projectSlug ?? ctx.projectDir,
|
|
60
|
+
projectVersion: resolveProjectVersion(ctx, contentContext),
|
|
61
|
+
projectDir: ctx.projectDir,
|
|
62
|
+
files,
|
|
63
|
+
developmentMode: contentContext?.sourceType === "branch",
|
|
64
|
+
})) {
|
|
65
|
+
candidates.add(cls);
|
|
49
66
|
}
|
|
50
67
|
return candidates;
|
|
51
68
|
}
|
|
@@ -11,5 +11,6 @@ export declare class StylesCSSHandler extends BaseHandler {
|
|
|
11
11
|
metadata: HandlerMetadata;
|
|
12
12
|
handle(req: dntShim.Request, ctx: HandlerContext): Promise<HandlerResult>;
|
|
13
13
|
private loadStylesheet;
|
|
14
|
+
private generateStylesheet;
|
|
14
15
|
}
|
|
15
16
|
//# sourceMappingURL=styles-css.handler.d.ts.map
|
|
@@ -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;
|
|
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;AAkBnG,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;IAkB5B,OAAO,CAAC,kBAAkB;CAe3B"}
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { BaseHandler } from "../response/base.js";
|
|
2
2
|
import { HTTP_OK, PRIORITY_HIGH_DEV } from "../../../utils/constants/index.js";
|
|
3
3
|
import { joinPath } from "../../../utils/path-utils.js";
|
|
4
|
-
import { formatCSSError, generateTailwindCSS, } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
4
|
+
import { formatCSSError, generateTailwindCSS, getProjectCSS, } from "../../../html/styles-builder/tailwind-compiler.js";
|
|
5
5
|
import { DEFAULT_STYLESHEET } from "../../../html/styles-builder/css-hash-cache.js";
|
|
6
6
|
import { serverLogger } from "../../../utils/index.js";
|
|
7
7
|
import { extractProjectCandidates } from "./styles-candidate-scanner.js";
|
|
@@ -39,10 +39,8 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
39
39
|
});
|
|
40
40
|
candidates = new Set();
|
|
41
41
|
}
|
|
42
|
-
const result = await
|
|
43
|
-
|
|
44
|
-
});
|
|
45
|
-
if (result.error) {
|
|
42
|
+
const result = await this.generateStylesheet(ctx, rawCss, candidates);
|
|
43
|
+
if ("error" in result && result.error) {
|
|
46
44
|
const formatted = formatCSSError(result.error);
|
|
47
45
|
logger.error("Tailwind error", {
|
|
48
46
|
error: formatted.message,
|
|
@@ -82,6 +80,8 @@ body::before {
|
|
|
82
80
|
logger.debug("CSS generated", {
|
|
83
81
|
candidates: candidates.size,
|
|
84
82
|
cssLength: result.css.length,
|
|
83
|
+
fromCache: "fromCache" in result ? result.fromCache : false,
|
|
84
|
+
cssHash: "hash" in result ? result.hash : undefined,
|
|
85
85
|
});
|
|
86
86
|
return this.respond(responseBuilder.withContentType("text/css; charset=utf-8", result.css, HTTP_OK));
|
|
87
87
|
});
|
|
@@ -114,4 +114,14 @@ body::before {
|
|
|
114
114
|
return DEFAULT_STYLESHEET;
|
|
115
115
|
}
|
|
116
116
|
}
|
|
117
|
+
generateStylesheet(ctx, rawCss, candidates) {
|
|
118
|
+
if (!ctx.projectSlug) {
|
|
119
|
+
return generateTailwindCSS(rawCss, candidates);
|
|
120
|
+
}
|
|
121
|
+
return getProjectCSS(ctx.projectSlug, rawCss, candidates, {
|
|
122
|
+
minify: true,
|
|
123
|
+
environment: "preview",
|
|
124
|
+
buildMode: "production",
|
|
125
|
+
});
|
|
126
|
+
}
|
|
117
127
|
}
|
package/package.json
CHANGED
package/src/deno.js
CHANGED
|
@@ -49,6 +49,9 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
49
49
|
|
|
50
50
|
/** Resolves when file list initialization is complete (for coordinating reads) */
|
|
51
51
|
private fileListReadyResolve: (() => void) | null = null;
|
|
52
|
+
/** Single-flight background rewarm when the file list cache disappears */
|
|
53
|
+
private fileListWarmupPromise: Promise<void> | null = null;
|
|
54
|
+
private fileListWarmupKey: string | null = null;
|
|
52
55
|
|
|
53
56
|
private projectData?: Project;
|
|
54
57
|
private apiBaseUrl: string;
|
|
@@ -140,6 +143,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
140
143
|
resultSize: result?.length ?? 0,
|
|
141
144
|
});
|
|
142
145
|
|
|
146
|
+
if (!result?.length) {
|
|
147
|
+
this.scheduleFileListWarmup("getFileList miss", cacheKey);
|
|
148
|
+
}
|
|
149
|
+
|
|
143
150
|
return result;
|
|
144
151
|
},
|
|
145
152
|
hasCachedFileList: async () => {
|
|
@@ -158,6 +165,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
158
165
|
resultSize: result?.length ?? 0,
|
|
159
166
|
});
|
|
160
167
|
|
|
168
|
+
if (!hasResult) {
|
|
169
|
+
this.scheduleFileListWarmup("hasCachedFileList miss", cacheKey);
|
|
170
|
+
}
|
|
171
|
+
|
|
161
172
|
return hasResult;
|
|
162
173
|
},
|
|
163
174
|
isPersistentCacheInvalidated: (prefix: string) => this.isPersistentCacheInvalidated(prefix),
|
|
@@ -202,6 +213,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
202
213
|
hasContent: result?.filter((f) => f.content)?.length ?? 0,
|
|
203
214
|
});
|
|
204
215
|
|
|
216
|
+
if (!result?.length) {
|
|
217
|
+
this.scheduleFileListWarmup("getFileListCache miss", cacheKey);
|
|
218
|
+
}
|
|
219
|
+
|
|
205
220
|
return result;
|
|
206
221
|
},
|
|
207
222
|
);
|
|
@@ -389,6 +404,73 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
389
404
|
return isPrefixBeingInvalidated(prefix);
|
|
390
405
|
}
|
|
391
406
|
|
|
407
|
+
private scheduleFileListWarmup(reason: string, cacheKey?: string): void {
|
|
408
|
+
if (!this.initialized || !this.contentContext) return;
|
|
409
|
+
|
|
410
|
+
const effectiveCacheKey = cacheKey ?? buildFileListCacheKey(this.contentContext);
|
|
411
|
+
|
|
412
|
+
if (this.fileListWarmupPromise && this.fileListWarmupKey === effectiveCacheKey) {
|
|
413
|
+
logger.debug("File list warmup already in progress", {
|
|
414
|
+
reason,
|
|
415
|
+
cacheKey: effectiveCacheKey,
|
|
416
|
+
});
|
|
417
|
+
return;
|
|
418
|
+
}
|
|
419
|
+
|
|
420
|
+
const warmupContext = this.contentContext;
|
|
421
|
+
let warmupPromise: Promise<void> | null = null;
|
|
422
|
+
warmupPromise = (async () => {
|
|
423
|
+
try {
|
|
424
|
+
const existing = await this.cache.getAsync<Array<{ path: string; content?: string }>>(
|
|
425
|
+
effectiveCacheKey,
|
|
426
|
+
);
|
|
427
|
+
|
|
428
|
+
if (existing?.length) {
|
|
429
|
+
logger.debug("Skipping file list warmup because cache is already populated", {
|
|
430
|
+
reason,
|
|
431
|
+
cacheKey: effectiveCacheKey,
|
|
432
|
+
fileCount: existing.length,
|
|
433
|
+
});
|
|
434
|
+
return;
|
|
435
|
+
}
|
|
436
|
+
|
|
437
|
+
logger.debug("Starting file list warmup", {
|
|
438
|
+
reason,
|
|
439
|
+
cacheKey: effectiveCacheKey,
|
|
440
|
+
sourceType: warmupContext.sourceType,
|
|
441
|
+
branch: warmupContext.branch,
|
|
442
|
+
environmentName: warmupContext.environmentName,
|
|
443
|
+
releaseId: warmupContext.releaseId,
|
|
444
|
+
});
|
|
445
|
+
|
|
446
|
+
const files = await fetchFileListForContext(this.client, warmupContext);
|
|
447
|
+
await this.cache.setAsync(effectiveCacheKey, files);
|
|
448
|
+
|
|
449
|
+
logger.debug("File list warmup complete", {
|
|
450
|
+
reason,
|
|
451
|
+
cacheKey: effectiveCacheKey,
|
|
452
|
+
totalFiles: files.length,
|
|
453
|
+
filesWithContent: files.filter((file) => file.content).length,
|
|
454
|
+
});
|
|
455
|
+
} catch (error) {
|
|
456
|
+
logger.warn("File list warmup failed", {
|
|
457
|
+
reason,
|
|
458
|
+
cacheKey: effectiveCacheKey,
|
|
459
|
+
error: error instanceof Error ? error.message : String(error),
|
|
460
|
+
});
|
|
461
|
+
} finally {
|
|
462
|
+
if (warmupPromise && this.fileListWarmupPromise === warmupPromise) {
|
|
463
|
+
this.fileListWarmupPromise = null;
|
|
464
|
+
this.fileListWarmupKey = null;
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
})();
|
|
468
|
+
|
|
469
|
+
this.fileListWarmupPromise = warmupPromise;
|
|
470
|
+
this.fileListWarmupKey = effectiveCacheKey;
|
|
471
|
+
this.readOps.setFileListReadyPromise(warmupPromise);
|
|
472
|
+
}
|
|
473
|
+
|
|
392
474
|
getPokeMetrics(): {
|
|
393
475
|
received: number;
|
|
394
476
|
invalidationsTriggered: number;
|
|
@@ -442,6 +524,8 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
442
524
|
this.statOps.clearIndex();
|
|
443
525
|
this.dirOps.clearTree();
|
|
444
526
|
this.initialized = false;
|
|
527
|
+
this.fileListWarmupPromise = null;
|
|
528
|
+
this.fileListWarmupKey = null;
|
|
445
529
|
|
|
446
530
|
logger.debug("Disposed");
|
|
447
531
|
}
|
|
@@ -473,6 +557,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
473
557
|
hasFiles: !!files,
|
|
474
558
|
fileCount: files?.length ?? 0,
|
|
475
559
|
});
|
|
560
|
+
this.scheduleFileListWarmup("getAllSourceFiles miss", cacheKey);
|
|
476
561
|
return [];
|
|
477
562
|
}
|
|
478
563
|
|
|
@@ -584,6 +669,8 @@ export class VeryfrontFSAdapter implements FSAdapter {
|
|
|
584
669
|
if (contextChanged) {
|
|
585
670
|
this.statOps.clearIndex();
|
|
586
671
|
this.dirOps.clearTree();
|
|
672
|
+
this.fileListWarmupPromise = null;
|
|
673
|
+
this.fileListWarmupKey = null;
|
|
587
674
|
logger.debug("Cleared index and dirTree due to context change", {
|
|
588
675
|
oldContext,
|
|
589
676
|
newContext: context,
|
|
@@ -23,6 +23,14 @@ interface RouteCandidateOptions {
|
|
|
23
23
|
developmentMode: boolean;
|
|
24
24
|
}
|
|
25
25
|
|
|
26
|
+
interface ProjectCandidateOptions {
|
|
27
|
+
projectScope: string;
|
|
28
|
+
projectVersion: string;
|
|
29
|
+
projectDir: string;
|
|
30
|
+
files: SourceFileLike[];
|
|
31
|
+
developmentMode: boolean;
|
|
32
|
+
}
|
|
33
|
+
|
|
26
34
|
const logger = rendererLogger.component("css-candidate-manifest");
|
|
27
35
|
const SOURCE_EXTENSIONS = [".tsx", ".jsx", ".mdx", ".ts", ".js"];
|
|
28
36
|
const DEV_MANIFEST_TTL_MS = 2_000;
|
|
@@ -90,6 +98,29 @@ function buildCandidateManifest(files: SourceFileLike[], projectDir: string): Ca
|
|
|
90
98
|
return { fileCandidates, allCandidates, builtAt: Date.now() };
|
|
91
99
|
}
|
|
92
100
|
|
|
101
|
+
function getOrBuildManifest(
|
|
102
|
+
options: Pick<
|
|
103
|
+
ProjectCandidateOptions,
|
|
104
|
+
"projectScope" | "projectVersion" | "projectDir" | "files" | "developmentMode"
|
|
105
|
+
>,
|
|
106
|
+
): CandidateManifest {
|
|
107
|
+
const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
|
|
108
|
+
const existingManifest = manifestCache.get(manifestKey);
|
|
109
|
+
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
110
|
+
? buildCandidateManifest(options.files, options.projectDir)
|
|
111
|
+
: existingManifest!;
|
|
112
|
+
|
|
113
|
+
if (manifest !== existingManifest) {
|
|
114
|
+
manifestCache.set(manifestKey, manifest);
|
|
115
|
+
|
|
116
|
+
for (const key of routeCandidateCache.keys()) {
|
|
117
|
+
if (key.startsWith(`${manifestKey}:`)) routeCandidateCache.delete(key);
|
|
118
|
+
}
|
|
119
|
+
}
|
|
120
|
+
|
|
121
|
+
return manifest;
|
|
122
|
+
}
|
|
123
|
+
|
|
93
124
|
function addCandidatesForPath(
|
|
94
125
|
target: Set<string>,
|
|
95
126
|
manifest: CandidateManifest,
|
|
@@ -109,20 +140,7 @@ function addCandidatesForPath(
|
|
|
109
140
|
*/
|
|
110
141
|
export function getRouteCandidates(options: RouteCandidateOptions): Set<string> {
|
|
111
142
|
const manifestKey = buildManifestCacheKey(options.projectScope, options.projectVersion);
|
|
112
|
-
const
|
|
113
|
-
const manifest = shouldRebuildManifest(existingManifest, options.developmentMode)
|
|
114
|
-
? buildCandidateManifest(options.files, options.projectDir)
|
|
115
|
-
: existingManifest!;
|
|
116
|
-
|
|
117
|
-
if (manifest !== existingManifest) {
|
|
118
|
-
manifestCache.set(manifestKey, manifest);
|
|
119
|
-
|
|
120
|
-
// Clear route subsets when project-level file manifest is rebuilt.
|
|
121
|
-
for (const key of routeCandidateCache.keys()) {
|
|
122
|
-
if (key.startsWith(`${manifestKey}:`)) routeCandidateCache.delete(key);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
|
|
143
|
+
const manifest = getOrBuildManifest(options);
|
|
126
144
|
const routeCacheKey = `${manifestKey}:${options.routeKey}`;
|
|
127
145
|
const cachedRoute = routeCandidateCache.get(routeCacheKey);
|
|
128
146
|
if (cachedRoute) return new Set(cachedRoute);
|
|
@@ -156,6 +174,14 @@ export function getRouteCandidates(options: RouteCandidateOptions): Set<string>
|
|
|
156
174
|
return new Set(routeCandidates);
|
|
157
175
|
}
|
|
158
176
|
|
|
177
|
+
/**
|
|
178
|
+
* Resolve full-project Tailwind candidates from a precomputed per-project manifest.
|
|
179
|
+
*/
|
|
180
|
+
export function getProjectCandidates(options: ProjectCandidateOptions): Set<string> {
|
|
181
|
+
const manifest = getOrBuildManifest(options);
|
|
182
|
+
return new Set(manifest.allCandidates);
|
|
183
|
+
}
|
|
184
|
+
|
|
159
185
|
/**
|
|
160
186
|
* Invalidate cached candidate manifests for one project scope (or all scopes).
|
|
161
187
|
*/
|
|
@@ -12,6 +12,8 @@ import { extractCandidates } from "../../../html/styles-builder/tailwind-compile
|
|
|
12
12
|
import { serverLogger } from "../../../utils/index.js";
|
|
13
13
|
import { createFileSystem } from "../../../platform/compat/fs.js";
|
|
14
14
|
import { join } from "../../../platform/compat/path/index.js";
|
|
15
|
+
import { getProjectCandidates } from "../../../rendering/orchestrator/css-candidate-manifest.js";
|
|
16
|
+
import type { ResolvedContentContext } from "../../../platform/adapters/fs/veryfront/types.js";
|
|
15
17
|
import type { HandlerContext } from "../types.js";
|
|
16
18
|
import { FRAMEWORK_CANDIDATES } from "./framework-candidates.generated.js";
|
|
17
19
|
|
|
@@ -23,6 +25,25 @@ const SKIP_DIRS = new Set(["node_modules", ".cache", ".git", "dist", "build", ".
|
|
|
23
25
|
/** De-duplicated set of framework candidates, computed once at import time. */
|
|
24
26
|
const frameworkCandidates = new Set<string>(FRAMEWORK_CANDIDATES);
|
|
25
27
|
|
|
28
|
+
interface SourceFileProvider {
|
|
29
|
+
getAllSourceFiles?: () =>
|
|
30
|
+
| Array<{ path: string; content?: string }>
|
|
31
|
+
| Promise<Array<{ path: string; content?: string }>>;
|
|
32
|
+
getContentContext?: () => ResolvedContentContext | null;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
function resolveProjectVersion(
|
|
36
|
+
ctx: HandlerContext,
|
|
37
|
+
contentContext: ResolvedContentContext | null,
|
|
38
|
+
): string {
|
|
39
|
+
if (contentContext?.releaseId) return `release:${contentContext.releaseId}`;
|
|
40
|
+
if (contentContext?.branch) return `branch:${contentContext.branch}`;
|
|
41
|
+
if (contentContext?.environmentName) return `environment:${contentContext.environmentName}`;
|
|
42
|
+
if (ctx.releaseId) return `release:${ctx.releaseId}`;
|
|
43
|
+
if (ctx.parsedDomain?.branch) return `branch:${ctx.parsedDomain.branch}`;
|
|
44
|
+
return "live";
|
|
45
|
+
}
|
|
46
|
+
|
|
26
47
|
/**
|
|
27
48
|
* Extract Tailwind CSS candidate class names from all project source files.
|
|
28
49
|
*
|
|
@@ -42,9 +63,8 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
42
63
|
|
|
43
64
|
// Call method directly on wrappedFs to preserve 'this' context
|
|
44
65
|
const fsAdapter = wrappedFs.getUnderlyingAdapter() as {
|
|
45
|
-
getAllSourceFiles?:
|
|
46
|
-
|
|
47
|
-
| Promise<Array<{ path: string; content?: string }>>;
|
|
66
|
+
getAllSourceFiles?: SourceFileProvider["getAllSourceFiles"];
|
|
67
|
+
getContentContext?: SourceFileProvider["getContentContext"];
|
|
48
68
|
};
|
|
49
69
|
|
|
50
70
|
if (typeof fsAdapter.getAllSourceFiles !== "function") {
|
|
@@ -56,14 +76,20 @@ export async function extractProjectCandidates(ctx: HandlerContext): Promise<Set
|
|
|
56
76
|
|
|
57
77
|
const candidates = new Set<string>(frameworkCandidates);
|
|
58
78
|
const files = await fsAdapter.getAllSourceFiles();
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
79
|
+
const contentContext = typeof fsAdapter.getContentContext === "function"
|
|
80
|
+
? fsAdapter.getContentContext()
|
|
81
|
+
: null;
|
|
82
|
+
|
|
83
|
+
for (
|
|
84
|
+
const cls of getProjectCandidates({
|
|
85
|
+
projectScope: ctx.projectSlug ?? contentContext?.projectSlug ?? ctx.projectDir,
|
|
86
|
+
projectVersion: resolveProjectVersion(ctx, contentContext),
|
|
87
|
+
projectDir: ctx.projectDir,
|
|
88
|
+
files,
|
|
89
|
+
developmentMode: contentContext?.sourceType === "branch",
|
|
90
|
+
})
|
|
91
|
+
) {
|
|
92
|
+
candidates.add(cls);
|
|
67
93
|
}
|
|
68
94
|
|
|
69
95
|
return candidates;
|
|
@@ -14,6 +14,7 @@ import { joinPath } from "../../../utils/path-utils.js";
|
|
|
14
14
|
import {
|
|
15
15
|
formatCSSError,
|
|
16
16
|
generateTailwindCSS,
|
|
17
|
+
getProjectCSS,
|
|
17
18
|
} from "../../../html/styles-builder/tailwind-compiler.js";
|
|
18
19
|
import { DEFAULT_STYLESHEET } from "../../../html/styles-builder/css-hash-cache.js";
|
|
19
20
|
import { serverLogger } from "../../../utils/index.js";
|
|
@@ -21,6 +22,10 @@ import { extractProjectCandidates } from "./styles-candidate-scanner.js";
|
|
|
21
22
|
|
|
22
23
|
const logger = serverLogger.component("styles-css-handler");
|
|
23
24
|
|
|
25
|
+
type GeneratedStylesResult =
|
|
26
|
+
| Awaited<ReturnType<typeof generateTailwindCSS>>
|
|
27
|
+
| Awaited<ReturnType<typeof getProjectCSS>>;
|
|
28
|
+
|
|
24
29
|
export class StylesCSSHandler extends BaseHandler {
|
|
25
30
|
metadata: HandlerMetadata = {
|
|
26
31
|
name: "StylesCSSHandler",
|
|
@@ -54,11 +59,9 @@ export class StylesCSSHandler extends BaseHandler {
|
|
|
54
59
|
});
|
|
55
60
|
candidates = new Set<string>();
|
|
56
61
|
}
|
|
57
|
-
const result = await
|
|
58
|
-
projectSlug: ctx.projectSlug,
|
|
59
|
-
});
|
|
62
|
+
const result = await this.generateStylesheet(ctx, rawCss, candidates);
|
|
60
63
|
|
|
61
|
-
if (result.error) {
|
|
64
|
+
if ("error" in result && result.error) {
|
|
62
65
|
const formatted = formatCSSError(result.error);
|
|
63
66
|
logger.error("Tailwind error", {
|
|
64
67
|
error: formatted.message,
|
|
@@ -104,6 +107,8 @@ body::before {
|
|
|
104
107
|
logger.debug("CSS generated", {
|
|
105
108
|
candidates: candidates.size,
|
|
106
109
|
cssLength: result.css.length,
|
|
110
|
+
fromCache: "fromCache" in result ? result.fromCache : false,
|
|
111
|
+
cssHash: "hash" in result ? result.hash : undefined,
|
|
107
112
|
});
|
|
108
113
|
|
|
109
114
|
return this.respond(
|
|
@@ -144,4 +149,20 @@ body::before {
|
|
|
144
149
|
return DEFAULT_STYLESHEET;
|
|
145
150
|
}
|
|
146
151
|
}
|
|
152
|
+
|
|
153
|
+
private generateStylesheet(
|
|
154
|
+
ctx: HandlerContext,
|
|
155
|
+
rawCss: string,
|
|
156
|
+
candidates: Set<string>,
|
|
157
|
+
): Promise<GeneratedStylesResult> {
|
|
158
|
+
if (!ctx.projectSlug) {
|
|
159
|
+
return generateTailwindCSS(rawCss, candidates);
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
return getProjectCSS(ctx.projectSlug, rawCss, candidates, {
|
|
163
|
+
minify: true,
|
|
164
|
+
environment: "preview",
|
|
165
|
+
buildMode: "production",
|
|
166
|
+
});
|
|
167
|
+
}
|
|
147
168
|
}
|