veryfront 0.1.92 → 0.1.94

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.
Files changed (97) hide show
  1. package/esm/cli/commands/styles/command-help.d.ts +3 -0
  2. package/esm/cli/commands/styles/command-help.d.ts.map +1 -0
  3. package/esm/cli/commands/styles/command-help.js +19 -0
  4. package/esm/cli/commands/styles/command.d.ts +3 -0
  5. package/esm/cli/commands/styles/command.d.ts.map +1 -0
  6. package/esm/cli/commands/styles/command.js +198 -0
  7. package/esm/cli/commands/styles/handler.d.ts +24 -0
  8. package/esm/cli/commands/styles/handler.d.ts.map +1 -0
  9. package/esm/cli/commands/styles/handler.js +17 -0
  10. package/esm/cli/help/command-definitions.d.ts.map +1 -1
  11. package/esm/cli/help/command-definitions.js +2 -0
  12. package/esm/cli/router.d.ts.map +1 -1
  13. package/esm/cli/router.js +2 -0
  14. package/esm/deno.d.ts +1 -0
  15. package/esm/deno.js +2 -1
  16. package/esm/src/html/styles-builder/css-pregeneration.d.ts +9 -0
  17. package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -1
  18. package/esm/src/html/styles-builder/css-pregeneration.js +26 -15
  19. package/esm/src/jobs/schemas.d.ts +40 -40
  20. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
  21. package/esm/src/platform/adapters/fs/veryfront/adapter.js +15 -3
  22. package/esm/src/platform/adapters/fs/veryfront/types.d.ts +2 -0
  23. package/esm/src/platform/adapters/fs/veryfront/types.d.ts.map +1 -1
  24. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +5 -1
  25. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
  26. package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +10 -2
  27. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +2 -1
  28. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
  29. package/esm/src/platform/adapters/veryfront-api-client/client.js +3 -0
  30. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +1 -1
  31. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
  32. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +11 -2
  33. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
  34. package/esm/src/platform/adapters/veryfront-api-client/operations.js +30 -0
  35. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +14 -3
  36. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
  37. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +8 -1
  38. package/esm/src/rendering/styles.d.ts +9 -0
  39. package/esm/src/rendering/styles.d.ts.map +1 -0
  40. package/esm/src/rendering/styles.js +8 -0
  41. package/esm/src/sandbox/index.d.ts +1 -1
  42. package/esm/src/sandbox/index.d.ts.map +1 -1
  43. package/esm/src/sandbox/sandbox.d.ts +14 -3
  44. package/esm/src/sandbox/sandbox.d.ts.map +1 -1
  45. package/esm/src/sandbox/sandbox.js +20 -6
  46. package/esm/src/security/http/base-handler.d.ts.map +1 -1
  47. package/esm/src/security/http/base-handler.js +5 -2
  48. package/esm/src/server/context/request-context.d.ts.map +1 -1
  49. package/esm/src/server/context/request-context.js +4 -2
  50. package/esm/src/server/handlers/dev/scripts/hmr-scripts.d.ts.map +1 -1
  51. package/esm/src/server/handlers/dev/scripts/hmr-scripts.js +91 -4
  52. package/esm/src/server/handlers/dev/styles-css.handler.d.ts +2 -0
  53. package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
  54. package/esm/src/server/handlers/dev/styles-css.handler.js +23 -0
  55. package/esm/src/server/handlers/preview/hmr-message-router.d.ts +2 -1
  56. package/esm/src/server/handlers/preview/hmr-message-router.d.ts.map +1 -1
  57. package/esm/src/server/handlers/preview/hmr-message-router.js +15 -5
  58. package/esm/src/server/handlers/preview/hmr.handler.js +1 -1
  59. package/esm/src/server/handlers/preview/markdown-preview.handler.d.ts.map +1 -1
  60. package/esm/src/server/handlers/preview/markdown-preview.handler.js +4 -2
  61. package/esm/src/server/handlers/request/css.handler.d.ts.map +1 -1
  62. package/esm/src/server/handlers/request/css.handler.js +4 -2
  63. package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
  64. package/esm/src/server/handlers/request/ssr/ssr.handler.js +4 -2
  65. package/esm/src/server/reload-notifier.d.ts +3 -1
  66. package/esm/src/server/reload-notifier.d.ts.map +1 -1
  67. package/esm/src/utils/version.d.ts +1 -1
  68. package/esm/src/utils/version.js +1 -1
  69. package/package.json +1 -1
  70. package/src/cli/commands/styles/command-help.ts +21 -0
  71. package/src/cli/commands/styles/command.ts +296 -0
  72. package/src/cli/commands/styles/handler.ts +23 -0
  73. package/src/cli/help/command-definitions.ts +2 -0
  74. package/src/cli/router.ts +2 -0
  75. package/src/deno.js +2 -1
  76. package/src/src/html/styles-builder/css-pregeneration.ts +57 -29
  77. package/src/src/platform/adapters/fs/veryfront/adapter.ts +18 -4
  78. package/src/src/platform/adapters/fs/veryfront/types.ts +2 -0
  79. package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +18 -3
  80. package/src/src/platform/adapters/veryfront-api-client/client.ts +8 -0
  81. package/src/src/platform/adapters/veryfront-api-client/index.ts +1 -0
  82. package/src/src/platform/adapters/veryfront-api-client/operations.ts +48 -2
  83. package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +9 -1
  84. package/src/src/rendering/styles.ts +15 -0
  85. package/src/src/sandbox/index.ts +1 -0
  86. package/src/src/sandbox/sandbox.ts +33 -6
  87. package/src/src/security/http/base-handler.ts +5 -2
  88. package/src/src/server/context/request-context.ts +4 -2
  89. package/src/src/server/handlers/dev/scripts/hmr-scripts.ts +93 -4
  90. package/src/src/server/handlers/dev/styles-css.handler.ts +31 -0
  91. package/src/src/server/handlers/preview/hmr-message-router.ts +15 -5
  92. package/src/src/server/handlers/preview/hmr.handler.ts +1 -1
  93. package/src/src/server/handlers/preview/markdown-preview.handler.ts +4 -2
  94. package/src/src/server/handlers/request/css.handler.ts +4 -2
  95. package/src/src/server/handlers/request/ssr/ssr.handler.ts +4 -2
  96. package/src/src/server/reload-notifier.ts +3 -1
  97. package/src/src/utils/version.ts +1 -1
@@ -0,0 +1,296 @@
1
+ import * as dntShim from "../../../_dnt.shims.js";
2
+ import { z } from "zod";
3
+ import { getConfig } from "../../../src/config/index.js";
4
+ import {
5
+ enhanceAdapterWithFS,
6
+ getEnv,
7
+ isExtendedFSAdapter,
8
+ runtime,
9
+ type VeryfrontApiClient,
10
+ } from "../../../src/platform/index.js";
11
+ import {
12
+ buildPreparedCSSArtifactFromFiles,
13
+ createStyleScopeProfile,
14
+ resolveStyleContentVersion,
15
+ } from "../../../src/rendering/styles.js";
16
+ import { cliLogger, exitProcess } from "../../utils/index.js";
17
+ import type { StylesArgs } from "./handler.js";
18
+ import { writeJobResultIfConfigured } from "../../utils/write-job-result.js";
19
+
20
+ const StyleArtifactBuildConfigSchema = z.object({
21
+ style_profile_hash: z.string().min(1).optional(),
22
+ branch: z.string().min(1).optional(),
23
+ environment_name: z.string().min(1).optional(),
24
+ release_id: z.string().min(1).optional(),
25
+ }).superRefine((value, ctx) => {
26
+ const selectorCount = [value.branch, value.environment_name, value.release_id].filter(Boolean)
27
+ .length;
28
+ if (selectorCount !== 1) {
29
+ ctx.addIssue({
30
+ code: z.ZodIssueCode.custom,
31
+ message: "Exactly one of branch, environment_name, or release_id is required.",
32
+ });
33
+ }
34
+ });
35
+
36
+ type StyleArtifactBuildConfig = z.infer<typeof StyleArtifactBuildConfigSchema>;
37
+
38
+ interface StyleBuildContentContext {
39
+ branch?: string;
40
+ environmentName?: string;
41
+ releaseId?: string;
42
+ }
43
+
44
+ interface StyleArtifactBuildResult {
45
+ kind: "style_artifact";
46
+ status: "ready";
47
+ style_profile_hash: string;
48
+ artifact_hash: string;
49
+ asset_path: string;
50
+ content_type: string;
51
+ etag: string;
52
+ selector: {
53
+ type: "branch" | "environment" | "release";
54
+ value: string;
55
+ };
56
+ }
57
+
58
+ type StyleArtifactSelector =
59
+ | { branch: string; environmentName?: never; releaseId?: never; type: "branch"; value: string }
60
+ | {
61
+ branch?: never;
62
+ environmentName: string;
63
+ releaseId?: never;
64
+ type: "environment";
65
+ value: string;
66
+ }
67
+ | { branch?: never; environmentName?: never; releaseId: string; type: "release"; value: string };
68
+
69
+ function parseStyleArtifactBuildConfig(rawConfig: string | undefined): StyleArtifactBuildConfig {
70
+ if (!rawConfig) {
71
+ throw new Error("Missing --config JSON");
72
+ }
73
+
74
+ let parsed: unknown;
75
+ try {
76
+ parsed = JSON.parse(rawConfig);
77
+ } catch {
78
+ throw new Error("Invalid --config JSON");
79
+ }
80
+
81
+ return StyleArtifactBuildConfigSchema.parse(parsed);
82
+ }
83
+
84
+ function resolveStyleArtifactSelector(
85
+ config: StyleArtifactBuildConfig,
86
+ ): StyleArtifactSelector {
87
+ if (config.branch) {
88
+ return {
89
+ branch: config.branch,
90
+ type: "branch",
91
+ value: config.branch,
92
+ };
93
+ }
94
+
95
+ if (config.environment_name) {
96
+ return {
97
+ environmentName: config.environment_name,
98
+ type: "environment",
99
+ value: config.environment_name,
100
+ };
101
+ }
102
+
103
+ return {
104
+ releaseId: config.release_id!,
105
+ type: "release",
106
+ value: config.release_id!,
107
+ };
108
+ }
109
+
110
+ function resolveContentContextSelector(
111
+ selector: StyleArtifactSelector,
112
+ ): { type: "branch"; branch: string } | { type: "environment"; name: string } | {
113
+ type: "release";
114
+ releaseId: string;
115
+ } {
116
+ if (selector.type === "branch") {
117
+ return { type: "branch", branch: selector.branch };
118
+ }
119
+
120
+ if (selector.type === "environment") {
121
+ return { type: "environment", name: selector.environmentName };
122
+ }
123
+
124
+ return { type: "release", releaseId: selector.releaseId };
125
+ }
126
+
127
+ function requireEnv(name: string): string {
128
+ const value = getEnv(name)?.trim();
129
+ if (!value) {
130
+ throw new Error(`${name} is required`);
131
+ }
132
+ return value;
133
+ }
134
+
135
+ function getUnderlyingVeryfrontClient(adapter: Awaited<ReturnType<typeof enhanceAdapterWithFS>>): {
136
+ client: VeryfrontApiClient;
137
+ contentContext: StyleBuildContentContext | null;
138
+ getAllSourceFiles: () => Promise<Array<{ path: string; content?: string }>>;
139
+ } {
140
+ if (!isExtendedFSAdapter(adapter.fs)) {
141
+ throw new Error("Styles build requires a Veryfront FS adapter");
142
+ }
143
+
144
+ const fsAdapter = adapter.fs.getUnderlyingAdapter() as {
145
+ getAllSourceFiles?: () => Promise<Array<{ path: string; content?: string }>>;
146
+ getContentContext?: () => StyleBuildContentContext | null;
147
+ getClient?: () => VeryfrontApiClient;
148
+ };
149
+
150
+ if (
151
+ typeof fsAdapter.getAllSourceFiles !== "function" ||
152
+ typeof fsAdapter.getClient !== "function"
153
+ ) {
154
+ throw new Error("Styles build requires a Veryfront source adapter");
155
+ }
156
+
157
+ return {
158
+ client: fsAdapter.getClient(),
159
+ contentContext: typeof fsAdapter.getContentContext === "function"
160
+ ? fsAdapter.getContentContext()
161
+ : null,
162
+ getAllSourceFiles: fsAdapter.getAllSourceFiles,
163
+ };
164
+ }
165
+
166
+ async function markStyleArtifactFailed(
167
+ client: VeryfrontApiClient | null,
168
+ selector: StyleArtifactSelector,
169
+ styleProfileHash: string | null | undefined,
170
+ error: string,
171
+ ): Promise<void> {
172
+ if (!client || !styleProfileHash) return;
173
+
174
+ try {
175
+ await client.upsertStyleArtifact({
176
+ ...(selector.type === "branch" ? { branch: selector.branch } : {}),
177
+ ...(selector.type === "environment" ? { environmentName: selector.environmentName } : {}),
178
+ ...(selector.type === "release" ? { releaseId: selector.releaseId } : {}),
179
+ styleProfileHash,
180
+ status: "failed",
181
+ failureReason: error,
182
+ });
183
+ } catch (updateError) {
184
+ cliLogger.warn("Failed to mark style artifact as failed", updateError);
185
+ }
186
+ }
187
+
188
+ export async function stylesCommand(options: StylesArgs): Promise<void> {
189
+ const buildConfig = parseStyleArtifactBuildConfig(options.config);
190
+ const selector = resolveStyleArtifactSelector(buildConfig);
191
+ const projectSlug = requireEnv("VERYFRONT_PROJECT_SLUG");
192
+ const apiToken = requireEnv("VERYFRONT_API_TOKEN");
193
+ const apiBaseUrl = requireEnv("VERYFRONT_API_BASE_URL");
194
+ const projectId = getEnv("VERYFRONT_PROJECT_ID")?.trim();
195
+ const projectDir = dntShim.Deno.cwd();
196
+
197
+ const baseAdapter = await runtime.get();
198
+ const adapter = await enhanceAdapterWithFS(
199
+ baseAdapter,
200
+ {
201
+ fs: {
202
+ type: "veryfront-api",
203
+ projectDir,
204
+ veryfront: {
205
+ apiBaseUrl,
206
+ apiToken,
207
+ projectSlug,
208
+ projectId,
209
+ contentSource: resolveContentContextSelector(selector),
210
+ },
211
+ },
212
+ },
213
+ projectDir,
214
+ );
215
+
216
+ let client: VeryfrontApiClient | null = null;
217
+ let resolvedStyleProfileHash: string | null = buildConfig.style_profile_hash ?? null;
218
+
219
+ try {
220
+ const sourceAdapter = getUnderlyingVeryfrontClient(adapter);
221
+ client = sourceAdapter.client;
222
+ const cacheKey = projectId || projectSlug;
223
+ const config = await getConfig(projectDir, adapter, { cacheKey });
224
+ const styleProfile = createStyleScopeProfile(config);
225
+ resolvedStyleProfileHash = styleProfile.hash;
226
+
227
+ if (buildConfig.style_profile_hash && styleProfile.hash !== buildConfig.style_profile_hash) {
228
+ const message =
229
+ `Style profile hash mismatch: expected ${buildConfig.style_profile_hash}, got ${styleProfile.hash}`;
230
+ await markStyleArtifactFailed(client, selector, buildConfig.style_profile_hash, message);
231
+ throw new Error(message);
232
+ }
233
+
234
+ const files = await sourceAdapter.getAllSourceFiles();
235
+ if (files.length === 0) {
236
+ throw new Error("No project files were available to build the style artifact");
237
+ }
238
+
239
+ const projectVersion = resolveStyleContentVersion(null, {
240
+ branch: sourceAdapter.contentContext?.branch ?? null,
241
+ releaseId: sourceAdapter.contentContext?.releaseId ?? null,
242
+ environmentName: sourceAdapter.contentContext?.environmentName ?? null,
243
+ });
244
+ const build = await buildPreparedCSSArtifactFromFiles({
245
+ projectSlug,
246
+ projectVersion,
247
+ projectDir,
248
+ files,
249
+ styleProfile,
250
+ stylesheetPath: config?.tailwind?.stylesheet,
251
+ minify: true,
252
+ environment: "preview",
253
+ buildMode: "production",
254
+ });
255
+
256
+ const resolution = await client.upsertStyleArtifact({
257
+ ...(selector.type === "branch" ? { branch: selector.branch } : {}),
258
+ ...(selector.type === "environment" ? { environmentName: selector.environmentName } : {}),
259
+ ...(selector.type === "release" ? { releaseId: selector.releaseId } : {}),
260
+ styleProfileHash: styleProfile.hash,
261
+ status: "ready",
262
+ artifactHash: build.hash,
263
+ });
264
+
265
+ const result: StyleArtifactBuildResult = {
266
+ kind: "style_artifact",
267
+ status: "ready",
268
+ style_profile_hash: styleProfile.hash,
269
+ artifact_hash: build.hash,
270
+ asset_path: resolution.assetPath ?? `/_vf/css/${build.hash}.css`,
271
+ content_type: resolution.contentType ?? "text/css; charset=utf-8",
272
+ etag: resolution.etag ?? `"${build.hash}"`,
273
+ selector: {
274
+ type: selector.type,
275
+ value: selector.value,
276
+ },
277
+ };
278
+
279
+ await writeJobResultIfConfigured(result);
280
+ cliLogger.info(`Built style artifact ${build.hash}`);
281
+ } catch (error) {
282
+ const message = error instanceof Error ? error.message : String(error);
283
+ await markStyleArtifactFailed(
284
+ client,
285
+ selector,
286
+ resolvedStyleProfileHash ?? buildConfig.style_profile_hash,
287
+ message,
288
+ );
289
+ cliLogger.error(message);
290
+ exitProcess(1);
291
+ } finally {
292
+ if (isExtendedFSAdapter(adapter.fs)) {
293
+ await adapter.fs.shutdown();
294
+ }
295
+ }
296
+ }
@@ -0,0 +1,23 @@
1
+ import { z } from "zod";
2
+ import { createArgParser, parseArgsOrThrow } from "../../shared/args.js";
3
+ import type { ParsedArgs } from "../../shared/types.js";
4
+
5
+ const StylesArgsSchema = z.object({
6
+ subcommand: z.literal("build-artifact"),
7
+ config: z.string().optional(),
8
+ debug: z.boolean().default(false),
9
+ });
10
+
11
+ export type StylesArgs = z.infer<typeof StylesArgsSchema>;
12
+
13
+ export const parseStylesArgs = createArgParser(StylesArgsSchema, {
14
+ subcommand: { keys: ["subcommand"], type: "string", positional: 0 },
15
+ config: { keys: ["config"], type: "string" },
16
+ debug: { keys: ["debug"], type: "boolean" },
17
+ });
18
+
19
+ export async function handleStylesCommand(args: ParsedArgs): Promise<void> {
20
+ const opts = parseArgsOrThrow(parseStylesArgs, "styles", args);
21
+ const { stylesCommand } = await import("./command.js");
22
+ await stylesCommand(opts);
23
+ }
@@ -16,6 +16,7 @@ import { doctorHelp } from "../commands/doctor/command-help.js";
16
16
  import { cleanHelp } from "../commands/clean/command-help.js";
17
17
  import { routesHelp } from "../commands/routes/command-help.js";
18
18
  import { studioHelp } from "../commands/studio/command-help.js";
19
+ import { stylesHelp } from "../commands/styles/command-help.js";
19
20
  import { lockHelp } from "../commands/lock/command-help.js";
20
21
  import { analyzeChunksHelp } from "../commands/analyze-chunks/command-help.js";
21
22
  import { generateHelp } from "../commands/generate/command-help.js";
@@ -51,6 +52,7 @@ export const COMMANDS: CommandRegistry = {
51
52
  clean: cleanHelp,
52
53
  routes: routesHelp,
53
54
  studio: studioHelp,
55
+ styles: stylesHelp,
54
56
  lock: lockHelp,
55
57
  "analyze-chunks": analyzeChunksHelp,
56
58
  generate: generateHelp,
package/src/cli/router.ts CHANGED
@@ -29,6 +29,7 @@ import { handleRoutesCommand } from "./commands/routes/handler.js";
29
29
  import { handleServeCommand } from "./commands/serve/handler.js";
30
30
  import { handleStartCommand } from "./commands/start/handler.js";
31
31
  import { handleStudioCommand } from "./commands/studio/handler.js";
32
+ import { handleStylesCommand } from "./commands/styles/handler.js";
32
33
  import { handleUpCommand } from "./commands/up/index.js";
33
34
  import { handleTaskCommand } from "./commands/task/handler.js";
34
35
  import { handleWorkflowCommand } from "./commands/workflow/handler.js";
@@ -55,6 +56,7 @@ const commands: Record<string, (args: ParsedArgs) => Promise<void>> = {
55
56
  "analyze-chunks": handleAnalyzeChunksCommand,
56
57
  "routes": handleRoutesCommand,
57
58
  "studio": handleStudioCommand,
59
+ "styles": handleStylesCommand,
58
60
  "lock": handleLockCommand,
59
61
  "generate": handleGenerateCommand,
60
62
  "g": handleGenerateCommand,
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.92",
3
+ "version": "0.1.94",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -97,6 +97,7 @@ export default {
97
97
  "veryfront/config": "./src/config/index.ts",
98
98
  "veryfront/build": "./src/build/index.ts",
99
99
  "veryfront/rendering": "./src/rendering/index.ts",
100
+ "veryfront/rendering/styles": "./src/rendering/styles.ts",
100
101
  "veryfront/cache": "./src/cache/index.ts",
101
102
  "veryfront/security": "./src/security/index.ts",
102
103
  "veryfront/errors": "./src/errors/index.ts",
@@ -39,6 +39,60 @@ interface CSSPregenerationOptions {
39
39
  buildMode?: "development" | "production";
40
40
  }
41
41
 
42
+ export interface PreparedCSSArtifactBuildResult {
43
+ css: string;
44
+ hash: string;
45
+ candidateCount: number;
46
+ fromCache: boolean;
47
+ context: ReturnType<typeof createPreparedProjectCSSContext>;
48
+ }
49
+
50
+ export async function buildPreparedCSSArtifactFromFiles(
51
+ options: CSSPregenerationOptions,
52
+ ): Promise<PreparedCSSArtifactBuildResult> {
53
+ const {
54
+ projectSlug,
55
+ projectVersion,
56
+ projectDir,
57
+ files,
58
+ styleProfile,
59
+ stylesheet,
60
+ stylesheetPath,
61
+ minify = true,
62
+ environment = "preview",
63
+ buildMode = "production",
64
+ } = options;
65
+
66
+ const resolvedStylesheet = stylesheet ?? findStylesheetFromFiles(files, stylesheetPath);
67
+ const candidates = extractCandidatesFromFiles(files, {
68
+ projectDir,
69
+ styleProfile,
70
+ });
71
+
72
+ const result = await getProjectCSS(projectSlug, resolvedStylesheet, candidates, {
73
+ minify,
74
+ environment,
75
+ buildMode,
76
+ });
77
+ const context = createPreparedProjectCSSContext(
78
+ projectSlug,
79
+ projectVersion,
80
+ resolvedStylesheet,
81
+ styleProfile.hash,
82
+ { minify, environment, buildMode },
83
+ );
84
+
85
+ await storePreparedProjectCSS(context, { css: result.css, hash: result.hash });
86
+
87
+ return {
88
+ css: result.css,
89
+ hash: result.hash,
90
+ candidateCount: candidates.size,
91
+ fromCache: result.fromCache,
92
+ context,
93
+ };
94
+ }
95
+
42
96
  /**
43
97
  * Pre-generate and cache CSS from file list.
44
98
  *
@@ -57,54 +111,28 @@ export async function pregenerateCSSFromFiles(
57
111
  const {
58
112
  projectSlug,
59
113
  projectVersion,
60
- projectDir,
61
114
  files,
62
115
  styleProfile,
63
116
  stylesheet,
64
- stylesheetPath,
65
- minify = true,
66
- environment = "preview",
67
- buildMode = "production",
68
117
  } = options;
69
118
  const startTime = performance.now();
70
119
 
71
120
  try {
72
- const resolvedStylesheet = stylesheet ?? findStylesheetFromFiles(files, stylesheetPath);
73
- const candidates = extractCandidatesFromFiles(files, {
74
- projectDir,
75
- styleProfile,
76
- });
77
-
78
121
  logger.debug("Starting", {
79
122
  projectSlug,
80
123
  projectVersion,
81
124
  fileCount: files.length,
82
- candidateCount: candidates.size,
83
- hasStylesheet: Boolean(resolvedStylesheet),
125
+ hasStylesheet: Boolean(stylesheet),
84
126
  styleProfileHash: styleProfile.hash,
85
127
  });
86
128
 
87
- const result = await getProjectCSS(projectSlug, resolvedStylesheet, candidates, {
88
- minify,
89
- environment,
90
- buildMode,
91
- });
92
- await storePreparedProjectCSS(
93
- createPreparedProjectCSSContext(
94
- projectSlug,
95
- projectVersion,
96
- resolvedStylesheet,
97
- styleProfile.hash,
98
- { minify, environment, buildMode },
99
- ),
100
- { css: result.css, hash: result.hash },
101
- );
129
+ const result = await buildPreparedCSSArtifactFromFiles(options);
102
130
  const duration = performance.now() - startTime;
103
131
 
104
132
  logger.debug("Complete", {
105
133
  projectSlug,
106
134
  projectVersion,
107
- candidateCount: candidates.size,
135
+ candidateCount: result.candidateCount,
108
136
  cssLength: result.css.length,
109
137
  cssHash: result.hash,
110
138
  fromCache: result.fromCache,
@@ -724,9 +724,9 @@ export class VeryfrontFSAdapter implements FSAdapter {
724
724
  */
725
725
  private async triggerCSSPregeneration(
726
726
  files: Array<{ path: string; content?: string }>,
727
- ): Promise<void> {
727
+ ): Promise<{ hash: string; assetPath: string } | undefined> {
728
728
  try {
729
- const { pregenerateCSSFromFiles, findStylesheetFromFiles } = await import(
729
+ const { buildPreparedCSSArtifactFromFiles, findStylesheetFromFiles } = await import(
730
730
  "../../../../html/styles-builder/css-pregeneration.js"
731
731
  );
732
732
  const { resolveStyleContentVersion } = await import(
@@ -744,7 +744,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
744
744
  logger.debug("Skipping CSS pre-generation without projectDir", {
745
745
  projectSlug: this.projectSlug,
746
746
  });
747
- return;
747
+ return undefined;
748
748
  }
749
749
 
750
750
  try {
@@ -769,7 +769,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
769
769
  environmentName: this.contentContext?.environmentName ?? null,
770
770
  });
771
771
 
772
- await pregenerateCSSFromFiles({
772
+ const result = await buildPreparedCSSArtifactFromFiles({
773
773
  projectSlug: this.projectSlug,
774
774
  projectVersion,
775
775
  projectDir,
@@ -781,11 +781,25 @@ export class VeryfrontFSAdapter implements FSAdapter {
781
781
  environment: "preview",
782
782
  buildMode: "production",
783
783
  });
784
+
785
+ logger.debug("CSS pre-generation complete", {
786
+ projectSlug: this.projectSlug,
787
+ projectVersion,
788
+ cssHash: result.hash,
789
+ candidateCount: result.candidateCount,
790
+ fromCache: result.fromCache,
791
+ });
792
+
793
+ return {
794
+ hash: result.hash,
795
+ assetPath: `/_vf/css/${result.hash}.css`,
796
+ };
784
797
  } catch (error) {
785
798
  logger.warn("CSS pre-generation failed", {
786
799
  projectSlug: this.projectSlug,
787
800
  error: error instanceof Error ? error.message : String(error),
788
801
  });
802
+ return undefined;
789
803
  }
790
804
  }
791
805
  }
@@ -120,6 +120,8 @@ export interface InvalidationProjectContext {
120
120
  environment?: "preview" | "production";
121
121
  branch?: string | null;
122
122
  releaseId?: string | null;
123
+ styleArtifactHash?: string;
124
+ styleAssetPath?: string;
123
125
  }
124
126
 
125
127
  export interface InvalidationCallbacks {
@@ -26,6 +26,11 @@ const WS_RECONNECT_MAX_FAILURES = 10;
26
26
  const WS_HEARTBEAT_INTERVAL_MS = 60000;
27
27
  const WS_HEARTBEAT_TIMEOUT_MS = 300000;
28
28
 
29
+ interface PreviewStyleArtifactInfo {
30
+ hash: string;
31
+ assetPath: string;
32
+ }
33
+
29
34
  interface WebSocketDeps {
30
35
  apiBaseUrl: string;
31
36
  apiToken: string;
@@ -43,7 +48,9 @@ interface WebSocketDeps {
43
48
  cacheKey: string,
44
49
  files: Array<{ path: string; content?: string }>,
45
50
  ) => Promise<void>;
46
- pregenerateStyles?: (files: Array<{ path: string; content?: string }>) => Promise<void>;
51
+ pregenerateStyles?: (
52
+ files: Array<{ path: string; content?: string }>,
53
+ ) => Promise<PreviewStyleArtifactInfo | undefined>;
47
54
  }
48
55
 
49
56
  export class WebSocketManager {
@@ -557,6 +564,7 @@ export class WebSocketManager {
557
564
  const contentContext = this.deps.getContentContext();
558
565
  const previewInvalidationPrefixes = this.getPreviewInvalidationPrefixes(contentContext);
559
566
  const previewInvalidationVersion = this.previewInvalidationVersion;
567
+ let preparedStyleArtifact: PreviewStyleArtifactInfo | undefined;
560
568
  let succeeded = false;
561
569
 
562
570
  try {
@@ -630,11 +638,12 @@ export class WebSocketManager {
630
638
  const cacheKey = buildFileListCacheKey(contentContext);
631
639
  await this.deps.setFileListCache(cacheKey, files);
632
640
  this.deps.clearFileListIndex();
633
- await this.deps.pregenerateStyles?.(files);
641
+ preparedStyleArtifact = await this.deps.pregenerateStyles?.(files);
634
642
 
635
643
  logger.debug("Fresh files cached (memory + Redis)", {
636
644
  cacheKey,
637
645
  fileCount: files.length,
646
+ styleAssetPath: preparedStyleArtifact?.assetPath,
638
647
  });
639
648
  } catch (error) {
640
649
  logger.warn("Failed to fetch files during selective invalidation", {
@@ -664,6 +673,8 @@ export class WebSocketManager {
664
673
  environment,
665
674
  branch: contentContext?.branch ?? null,
666
675
  releaseId: contentContext?.releaseId ?? null,
676
+ styleArtifactHash: preparedStyleArtifact?.hash,
677
+ styleAssetPath: preparedStyleArtifact?.assetPath,
667
678
  };
668
679
 
669
680
  this.deps.invalidationCallbacks.triggerReload?.(changedPaths, projectContext);
@@ -691,6 +702,7 @@ export class WebSocketManager {
691
702
  const contentContext = this.deps.getContentContext();
692
703
  const previewInvalidationPrefixes = this.getPreviewInvalidationPrefixes(contentContext);
693
704
  const previewInvalidationVersion = this.previewInvalidationVersion;
705
+ let preparedStyleArtifact: PreviewStyleArtifactInfo | undefined;
694
706
  let succeeded = false;
695
707
 
696
708
  try {
@@ -773,11 +785,12 @@ export class WebSocketManager {
773
785
  const cacheKey = buildFileListCacheKey(contentContext);
774
786
  await this.deps.setFileListCache(cacheKey, files);
775
787
  this.deps.clearFileListIndex();
776
- await this.deps.pregenerateStyles?.(files);
788
+ preparedStyleArtifact = await this.deps.pregenerateStyles?.(files);
777
789
 
778
790
  logger.debug("FRESH FILES FETCHED", {
779
791
  cacheKey,
780
792
  fileCount: files.length,
793
+ styleAssetPath: preparedStyleArtifact?.assetPath,
781
794
  });
782
795
  } catch (error) {
783
796
  logger.warn("Failed to fetch files during invalidation", { error });
@@ -801,6 +814,8 @@ export class WebSocketManager {
801
814
  environment,
802
815
  branch: contentContext?.branch ?? null,
803
816
  releaseId: contentContext?.releaseId ?? null,
817
+ styleArtifactHash: preparedStyleArtifact?.hash,
818
+ styleAssetPath: preparedStyleArtifact?.assetPath,
804
819
  };
805
820
 
806
821
  this.deps.invalidationCallbacks.triggerReload?.(undefined, projectContext);
@@ -1,5 +1,6 @@
1
1
  import { logger as baseLogger } from "../../../utils/index.js";
2
2
  import {
3
+ type EnsureStyleArtifactBuildInput,
3
4
  type FileDetail,
4
5
  type FileListResult,
5
6
  type ListFilesOptions,
@@ -350,6 +351,13 @@ export class VeryfrontApiClient {
350
351
  return this.operations.resolveStyleArtifact(projectRef, input);
351
352
  }
352
353
 
354
+ ensureStyleArtifactBuild(
355
+ input: EnsureStyleArtifactBuildInput,
356
+ projectRef = this.getProjectSlug()!,
357
+ ): Promise<ProjectStyleArtifactResolution> {
358
+ return this.operations.ensureStyleArtifactBuild(projectRef, input);
359
+ }
360
+
353
361
  upsertStyleArtifact(
354
362
  input: UpsertStyleArtifactInput,
355
363
  projectRef = this.getProjectSlug()!,
@@ -6,6 +6,7 @@
6
6
 
7
7
  export { type FileContext, VeryfrontApiClient } from "./client.js";
8
8
  export {
9
+ type EnsureStyleArtifactBuildInput,
9
10
  type FileDetail,
10
11
  type FileListResult,
11
12
  type ListFilesOptions,