veryfront 0.1.92 → 0.1.93

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 (75) 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/veryfront-api-client/client.d.ts +2 -1
  21. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
  22. package/esm/src/platform/adapters/veryfront-api-client/client.js +3 -0
  23. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +1 -1
  24. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
  25. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +11 -2
  26. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
  27. package/esm/src/platform/adapters/veryfront-api-client/operations.js +30 -0
  28. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +14 -3
  29. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
  30. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +8 -1
  31. package/esm/src/rendering/styles.d.ts +9 -0
  32. package/esm/src/rendering/styles.d.ts.map +1 -0
  33. package/esm/src/rendering/styles.js +8 -0
  34. package/esm/src/sandbox/index.d.ts +1 -1
  35. package/esm/src/sandbox/index.d.ts.map +1 -1
  36. package/esm/src/sandbox/sandbox.d.ts +14 -3
  37. package/esm/src/sandbox/sandbox.d.ts.map +1 -1
  38. package/esm/src/sandbox/sandbox.js +20 -6
  39. package/esm/src/security/http/base-handler.d.ts.map +1 -1
  40. package/esm/src/security/http/base-handler.js +5 -2
  41. package/esm/src/server/context/request-context.d.ts.map +1 -1
  42. package/esm/src/server/context/request-context.js +4 -2
  43. package/esm/src/server/handlers/dev/styles-css.handler.d.ts +2 -0
  44. package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
  45. package/esm/src/server/handlers/dev/styles-css.handler.js +23 -0
  46. package/esm/src/server/handlers/preview/markdown-preview.handler.d.ts.map +1 -1
  47. package/esm/src/server/handlers/preview/markdown-preview.handler.js +4 -2
  48. package/esm/src/server/handlers/request/css.handler.d.ts.map +1 -1
  49. package/esm/src/server/handlers/request/css.handler.js +4 -2
  50. package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
  51. package/esm/src/server/handlers/request/ssr/ssr.handler.js +4 -2
  52. package/esm/src/utils/version.d.ts +1 -1
  53. package/esm/src/utils/version.js +1 -1
  54. package/package.json +1 -1
  55. package/src/cli/commands/styles/command-help.ts +21 -0
  56. package/src/cli/commands/styles/command.ts +296 -0
  57. package/src/cli/commands/styles/handler.ts +23 -0
  58. package/src/cli/help/command-definitions.ts +2 -0
  59. package/src/cli/router.ts +2 -0
  60. package/src/deno.js +2 -1
  61. package/src/src/html/styles-builder/css-pregeneration.ts +57 -29
  62. package/src/src/platform/adapters/veryfront-api-client/client.ts +8 -0
  63. package/src/src/platform/adapters/veryfront-api-client/index.ts +1 -0
  64. package/src/src/platform/adapters/veryfront-api-client/operations.ts +48 -2
  65. package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +9 -1
  66. package/src/src/rendering/styles.ts +15 -0
  67. package/src/src/sandbox/index.ts +1 -0
  68. package/src/src/sandbox/sandbox.ts +33 -6
  69. package/src/src/security/http/base-handler.ts +5 -2
  70. package/src/src/server/context/request-context.ts +4 -2
  71. package/src/src/server/handlers/dev/styles-css.handler.ts +31 -0
  72. package/src/src/server/handlers/preview/markdown-preview.handler.ts +4 -2
  73. package/src/src/server/handlers/request/css.handler.ts +4 -2
  74. package/src/src/server/handlers/request/ssr/ssr.handler.ts +4 -2
  75. package/src/src/utils/version.ts +1 -1
@@ -2,7 +2,7 @@ import { BaseHandler } from "../../response/base.js";
2
2
  import { PRIORITY_LOW } from "../../../../utils/constants/index.js";
3
3
  import { generateNonce } from "../../../../security/http/response/security-handler.js";
4
4
  import { isExtendedFSAdapter } from "../../../../platform/adapters/fs/wrapper.js";
5
- import { getEnv } from "../../../../platform/compat/process.js";
5
+ import { getHostEnv } from "../../../../platform/compat/process.js";
6
6
  import { shouldUseNoCacheHeadersFromHandler } from "../../../context/enriched-context.js";
7
7
  import { withSpan } from "../../../../observability/tracing/otlp-setup.js";
8
8
  import { serverLogger } from "../../../../utils/index.js";
@@ -76,7 +76,9 @@ export class SSRHandler extends BaseHandler {
76
76
  if (ctx.projectSlug && isExtended && fsAdapter.isMultiProjectMode()) {
77
77
  const prodMode = isProductionMode(ctx, url);
78
78
  const branch = ctx.parsedDomain?.branch ?? null;
79
- const effectiveToken = ctx.proxyToken || getEnv("VERYFRONT_API_TOKEN") || "";
79
+ // Framework-owned token: bypass project env overlay so proxy mode works
80
+ // when a remote project overlay is active.
81
+ const effectiveToken = ctx.proxyToken || getHostEnv("VERYFRONT_API_TOKEN") || "";
80
82
  logger.debug("Using multi-project context", {
81
83
  projectSlug: ctx.projectSlug,
82
84
  productionMode: prodMode,
@@ -1,4 +1,4 @@
1
- export declare const VERSION = "0.1.92";
1
+ export declare const VERSION = "0.1.93";
2
2
  export declare function normalizeVeryfrontVersion(version: string | undefined): string | undefined;
3
3
  export declare function resolveRuntimeVersion(options?: {
4
4
  veryfrontVersion?: string;
@@ -2,7 +2,7 @@ import denoConfig from "../../deno.js";
2
2
  import { getEnv } from "../platform/compat/process.js";
3
3
  // Keep in sync with deno.json version.
4
4
  // scripts/release.ts updates this constant during releases.
5
- export const VERSION = "0.1.92";
5
+ export const VERSION = "0.1.93";
6
6
  export function normalizeVeryfrontVersion(version) {
7
7
  if (!version)
8
8
  return undefined;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.92",
3
+ "version": "0.1.93",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
@@ -0,0 +1,21 @@
1
+ import type { CommandHelp } from "../../help/types.js";
2
+
3
+ export const stylesHelp: CommandHelp = {
4
+ name: "styles",
5
+ description: "Build project CSS artifacts",
6
+ usage: "veryfront styles build-artifact --config <json>",
7
+ options: [
8
+ {
9
+ flag: "--config <json>",
10
+ description: "JSON build config with exactly one selector and an optional style_profile_hash",
11
+ },
12
+ {
13
+ flag: "--debug",
14
+ description: "Enable debug logging",
15
+ },
16
+ ],
17
+ examples: [
18
+ 'veryfront styles build-artifact --config \'{"branch":"main"}\'',
19
+ 'veryfront styles build-artifact --config \'{"style_profile_hash":"profile-1","environment_name":"Production"}\'',
20
+ ],
21
+ };
@@ -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.93",
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,
@@ -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,
@@ -64,19 +64,28 @@ export interface ResolveStyleArtifactInput extends StyleArtifactSelector {
64
64
  styleProfileHash: string;
65
65
  }
66
66
 
67
+ export interface EnsureStyleArtifactBuildInput extends ResolveStyleArtifactInput {
68
+ force?: boolean;
69
+ }
70
+
67
71
  export interface UpsertStyleArtifactInput extends ResolveStyleArtifactInput {
68
- artifactHash: string;
72
+ status?: "building" | "ready" | "failed";
73
+ artifactHash?: string;
69
74
  assetPath?: string;
70
75
  contentType?: string;
71
76
  etag?: string;
77
+ buildJobId?: string;
78
+ failureReason?: string;
72
79
  }
73
80
 
74
81
  export interface ProjectStyleArtifactResolution {
75
- status: "ready" | "missing";
82
+ status: "ready" | "missing" | "building" | "failed";
76
83
  artifactHash?: string;
77
84
  assetPath?: string;
78
85
  etag?: string;
79
86
  contentType?: string;
87
+ buildJobId?: string;
88
+ failureReason?: string;
80
89
  updatedAt?: string;
81
90
  }
82
91
 
@@ -128,6 +137,8 @@ function mapStyleArtifactResolution(raw: unknown): ProjectStyleArtifactResolutio
128
137
  assetPath: response.asset_path,
129
138
  etag: response.etag,
130
139
  contentType: response.content_type,
140
+ buildJobId: response.build_job_id,
141
+ failureReason: response.failure_reason,
131
142
  updatedAt: response.updated_at,
132
143
  };
133
144
  }
@@ -491,6 +502,37 @@ export class VeryfrontAPIOperations {
491
502
  return mapStyleArtifactResolution(await this.request(url));
492
503
  }
493
504
 
505
+ async ensureStyleArtifactBuild(
506
+ projectRef: string,
507
+ input: EnsureStyleArtifactBuildInput,
508
+ ): Promise<ProjectStyleArtifactResolution> {
509
+ const url = `/projects/${encodeURIComponent(projectRef)}/style-artifacts/current/builds`;
510
+ logger.debug("ensureStyleArtifactBuild", {
511
+ projectRef,
512
+ branch: input.branch,
513
+ environmentName: input.environmentName,
514
+ releaseId: input.releaseId,
515
+ styleProfileHash: input.styleProfileHash,
516
+ force: input.force ?? false,
517
+ });
518
+
519
+ return mapStyleArtifactResolution(
520
+ await this.request(url, {
521
+ method: "POST",
522
+ headers: {
523
+ "Content-Type": "application/json",
524
+ },
525
+ body: JSON.stringify({
526
+ style_profile_hash: input.styleProfileHash,
527
+ branch: input.branch,
528
+ environment_name: input.environmentName,
529
+ release_id: input.releaseId,
530
+ force: input.force ?? false,
531
+ }),
532
+ }),
533
+ );
534
+ }
535
+
494
536
  async upsertStyleArtifact(
495
537
  projectRef: string,
496
538
  input: UpsertStyleArtifactInput,
@@ -502,6 +544,7 @@ export class VeryfrontAPIOperations {
502
544
  environmentName: input.environmentName,
503
545
  releaseId: input.releaseId,
504
546
  styleProfileHash: input.styleProfileHash,
547
+ status: input.status ?? "ready",
505
548
  artifactHash: input.artifactHash,
506
549
  });
507
550
 
@@ -516,10 +559,13 @@ export class VeryfrontAPIOperations {
516
559
  branch: input.branch,
517
560
  environment_name: input.environmentName,
518
561
  release_id: input.releaseId,
562
+ status: input.status ?? "ready",
519
563
  artifact_hash: input.artifactHash,
520
564
  asset_path: input.assetPath,
521
565
  content_type: input.contentType,
522
566
  etag: input.etag,
567
+ build_job_id: input.buildJobId,
568
+ failure_reason: input.failureReason,
523
569
  }),
524
570
  }),
525
571
  );