veryfront 0.1.90 → 0.1.92

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 (66) hide show
  1. package/esm/deno.js +1 -1
  2. package/esm/src/config/environment-config.js +1 -1
  3. package/esm/src/jobs/schemas.d.ts +44 -44
  4. package/esm/src/jobs/schemas.js +3 -3
  5. package/esm/src/observability/metrics/manager.js +2 -2
  6. package/esm/src/observability/tracing/otlp-setup.js +2 -2
  7. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts +1 -0
  8. package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
  9. package/esm/src/platform/adapters/fs/veryfront/adapter.js +11 -5
  10. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +3 -1
  11. package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
  12. package/esm/src/platform/adapters/veryfront-api-client/client.js +6 -0
  13. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +2 -2
  14. package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
  15. package/esm/src/platform/adapters/veryfront-api-client/index.js +1 -1
  16. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +24 -0
  17. package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
  18. package/esm/src/platform/adapters/veryfront-api-client/operations.js +65 -3
  19. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +28 -0
  20. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
  21. package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +13 -0
  22. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts +1 -1
  23. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.d.ts.map +1 -1
  24. package/esm/src/platform/adapters/veryfront-api-client/schemas/index.js +1 -1
  25. package/esm/src/proxy/logger.d.ts.map +1 -1
  26. package/esm/src/proxy/logger.js +2 -6
  27. package/esm/src/proxy/tracing.d.ts.map +1 -1
  28. package/esm/src/proxy/tracing.js +2 -5
  29. package/esm/src/proxy/version.d.ts +2 -0
  30. package/esm/src/proxy/version.d.ts.map +1 -0
  31. package/esm/src/proxy/version.js +18 -0
  32. package/esm/src/sandbox/index.d.ts +1 -1
  33. package/esm/src/sandbox/index.d.ts.map +1 -1
  34. package/esm/src/sandbox/index.js +1 -1
  35. package/esm/src/sandbox/sandbox.d.ts +58 -0
  36. package/esm/src/sandbox/sandbox.d.ts.map +1 -1
  37. package/esm/src/sandbox/sandbox.js +111 -0
  38. package/esm/src/server/handlers/dev/styles-css.handler.d.ts +5 -0
  39. package/esm/src/server/handlers/dev/styles-css.handler.d.ts.map +1 -1
  40. package/esm/src/server/handlers/dev/styles-css.handler.js +121 -1
  41. package/esm/src/server/handlers/monitoring/health.handler.js +2 -2
  42. package/esm/src/utils/logger/logger.js +2 -2
  43. package/esm/src/utils/version.d.ts +9 -1
  44. package/esm/src/utils/version.d.ts.map +1 -1
  45. package/esm/src/utils/version.js +29 -2
  46. package/package.json +1 -1
  47. package/src/deno.js +1 -1
  48. package/src/src/config/environment-config.ts +1 -1
  49. package/src/src/jobs/schemas.ts +3 -3
  50. package/src/src/observability/metrics/manager.ts +2 -2
  51. package/src/src/observability/tracing/otlp-setup.ts +2 -2
  52. package/src/src/platform/adapters/fs/veryfront/adapter.ts +12 -5
  53. package/src/src/platform/adapters/veryfront-api-client/client.ts +17 -0
  54. package/src/src/platform/adapters/veryfront-api-client/index.ts +6 -0
  55. package/src/src/platform/adapters/veryfront-api-client/operations.ts +110 -3
  56. package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +16 -0
  57. package/src/src/platform/adapters/veryfront-api-client/schemas/index.ts +2 -0
  58. package/src/src/proxy/logger.ts +2 -8
  59. package/src/src/proxy/tracing.ts +2 -6
  60. package/src/src/proxy/version.ts +21 -0
  61. package/src/src/sandbox/index.ts +13 -1
  62. package/src/src/sandbox/sandbox.ts +183 -0
  63. package/src/src/server/handlers/dev/styles-css.handler.ts +179 -1
  64. package/src/src/server/handlers/monitoring/health.handler.ts +2 -2
  65. package/src/src/utils/logger/logger.ts +2 -2
  66. package/src/src/utils/version.ts +37 -2
@@ -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, getProjectCSS } from "../../../html/styles-builder/tailwind-compiler.js";
4
+ import { formatCSSError, getCSSByHashAsync, getProjectCSS, regenerateCSSByHash, } from "../../../html/styles-builder/tailwind-compiler.js";
5
5
  import { DEFAULT_STYLESHEET } from "../../../html/styles-builder/css-hash-cache.js";
6
6
  import { resolveStyleContentVersion } from "../../../html/styles-builder/content-version.js";
7
7
  import { createPreparedProjectCSSContext, storePreparedProjectCSS, tryGetPreparedProjectCSS, } from "../../../html/styles-builder/prepared-project-css-cache.js";
@@ -48,6 +48,15 @@ export class StylesCSSHandler extends BaseHandler {
48
48
  return this.respond(responseBuilder.withContentType("text/css; charset=utf-8", prepared.css, HTTP_OK));
49
49
  }
50
50
  }
51
+ const remotePrepared = await this.tryResolveRemotePreparedCSS(ctx, projectScope, styleProfile.hash, contentContext, preparedContext);
52
+ if (remotePrepared) {
53
+ logger.debug("Prepared CSS resolved via style artifact metadata", {
54
+ projectScope,
55
+ styleProfileHash: styleProfile.hash,
56
+ cssHash: remotePrepared.hash,
57
+ });
58
+ return this.respond(responseBuilder.withContentType("text/css; charset=utf-8", remotePrepared.css, HTTP_OK));
59
+ }
51
60
  let candidates;
52
61
  try {
53
62
  candidates = await extractProjectCandidates(ctx);
@@ -112,6 +121,9 @@ body::before {
112
121
  hash: result.hash,
113
122
  });
114
123
  }
124
+ if ("hash" in result) {
125
+ await this.registerPreparedCSSArtifact(ctx, styleProfile.hash, contentContext, result.hash);
126
+ }
115
127
  return this.respond(responseBuilder.withContentType("text/css; charset=utf-8", result.css, HTTP_OK));
116
128
  });
117
129
  }
@@ -158,6 +170,13 @@ body::before {
158
170
  const fsAdapter = wrappedFs.getUnderlyingAdapter();
159
171
  return typeof fsAdapter.getContentContext === "function" ? fsAdapter.getContentContext() : null;
160
172
  }
173
+ getVeryfrontApiClient(ctx) {
174
+ const wrappedFs = ctx.adapter.fs;
175
+ if (typeof wrappedFs.getUnderlyingAdapter !== "function")
176
+ return null;
177
+ const fsAdapter = wrappedFs.getUnderlyingAdapter();
178
+ return typeof fsAdapter.getClient === "function" ? fsAdapter.getClient() : null;
179
+ }
161
180
  createPreparedCSSContext(projectScope, rawCss, styleProfileHash, contentContext, ctx) {
162
181
  if (!projectScope)
163
182
  return undefined;
@@ -171,4 +190,105 @@ body::before {
171
190
  buildMode: "production",
172
191
  });
173
192
  }
193
+ resolveStyleArtifactSelector(contentContext, ctx) {
194
+ if (contentContext?.sourceType === "branch" && contentContext.branch) {
195
+ return {
196
+ branch: contentContext.branch,
197
+ };
198
+ }
199
+ if (contentContext?.sourceType === "environment" && contentContext.environmentName) {
200
+ return {
201
+ environmentName: contentContext.environmentName,
202
+ };
203
+ }
204
+ if (contentContext?.sourceType === "release" && contentContext.releaseId) {
205
+ return {
206
+ releaseId: contentContext.releaseId,
207
+ };
208
+ }
209
+ if (ctx.parsedDomain?.branch) {
210
+ return {
211
+ branch: ctx.parsedDomain.branch,
212
+ };
213
+ }
214
+ if (ctx.environmentName) {
215
+ return {
216
+ environmentName: ctx.environmentName,
217
+ };
218
+ }
219
+ if (ctx.releaseId) {
220
+ return {
221
+ releaseId: ctx.releaseId,
222
+ };
223
+ }
224
+ return null;
225
+ }
226
+ async tryResolveRemotePreparedCSS(ctx, projectScope, styleProfileHash, contentContext, preparedContext) {
227
+ if (!projectScope)
228
+ return undefined;
229
+ const selector = this.resolveStyleArtifactSelector(contentContext, ctx);
230
+ if (!selector)
231
+ return undefined;
232
+ const client = this.getVeryfrontApiClient(ctx);
233
+ if (!client)
234
+ return undefined;
235
+ try {
236
+ const resolved = await client.resolveStyleArtifact({
237
+ ...selector,
238
+ styleProfileHash,
239
+ });
240
+ if (resolved.status !== "ready" || !resolved.artifactHash) {
241
+ return undefined;
242
+ }
243
+ const css = await this.getPreparedCSSByHash(resolved.artifactHash, projectScope);
244
+ if (!css)
245
+ return undefined;
246
+ if (preparedContext) {
247
+ await storePreparedProjectCSS(preparedContext, {
248
+ css,
249
+ hash: resolved.artifactHash,
250
+ });
251
+ }
252
+ return {
253
+ css,
254
+ hash: resolved.artifactHash,
255
+ };
256
+ }
257
+ catch (error) {
258
+ logger.debug("Failed to resolve prepared CSS via style artifact metadata", {
259
+ projectScope,
260
+ styleProfileHash,
261
+ error: error instanceof Error ? error.message : String(error),
262
+ });
263
+ return undefined;
264
+ }
265
+ }
266
+ async getPreparedCSSByHash(cssHash, projectScope) {
267
+ const cached = await getCSSByHashAsync(cssHash);
268
+ if (cached)
269
+ return cached;
270
+ return regenerateCSSByHash(cssHash, projectScope);
271
+ }
272
+ async registerPreparedCSSArtifact(ctx, styleProfileHash, contentContext, cssHash) {
273
+ const selector = this.resolveStyleArtifactSelector(contentContext, ctx);
274
+ if (!selector)
275
+ return;
276
+ const client = this.getVeryfrontApiClient(ctx);
277
+ if (!client)
278
+ return;
279
+ try {
280
+ await client.upsertStyleArtifact({
281
+ ...selector,
282
+ styleProfileHash,
283
+ artifactHash: cssHash,
284
+ });
285
+ }
286
+ catch (error) {
287
+ logger.debug("Failed to register prepared CSS artifact", {
288
+ cssHash,
289
+ styleProfileHash,
290
+ error: error instanceof Error ? error.message : String(error),
291
+ });
292
+ }
293
+ }
174
294
  }
@@ -2,7 +2,7 @@ import { BaseHandler } from "../response/base.js";
2
2
  import { joinPath } from "../../../utils/path-utils.js";
3
3
  import { HTTP_OK, HTTP_UNAVAILABLE, PRIORITY_HIGH } from "../../../utils/constants/index.js";
4
4
  import { isTracingDegraded, isTracingEnabled } from "../../../observability/tracing/index.js";
5
- import { VERSION } from "../../../utils/version.js";
5
+ import { RUNTIME_VERSION } from "../../../utils/version.js";
6
6
  let serverInitialized = false;
7
7
  export function setServerInitialized(ready) {
8
8
  serverInitialized = ready;
@@ -57,7 +57,7 @@ export class HealthHandler extends BaseHandler {
57
57
  status: tracingDegraded ? "degraded" : "ok",
58
58
  timestamp: new Date().toISOString(),
59
59
  mode: hasStaticBuild ? "static+ssr" : "ssr",
60
- version: VERSION,
60
+ version: RUNTIME_VERSION,
61
61
  tracing: {
62
62
  enabled: isTracingEnabled(),
63
63
  degraded: tracingDegraded,
@@ -1,7 +1,7 @@
1
1
  import * as dntShim from "../../../_dnt.shims.js";
2
2
  import { getEnv } from "../../platform/compat/process.js";
3
3
  import { hasDenoRuntime, hasNodeProcess } from "../runtime-guards.js";
4
- import { VERSION } from "../version.js";
4
+ import { RUNTIME_VERSION } from "../version.js";
5
5
  import { ANSI, colorize, formatContextText, formatTimestamp, LEVEL_COLORS, LEVEL_GLYPHS, padTag, serializeError, } from "./core.js";
6
6
  export var LogLevel;
7
7
  (function (LogLevel) {
@@ -165,7 +165,7 @@ class ConsoleLogger {
165
165
  timestamp: new Date().toISOString(),
166
166
  level,
167
167
  service: this.prefix.toLowerCase(),
168
- veryfrontVersion: VERSION,
168
+ veryfrontVersion: RUNTIME_VERSION,
169
169
  message,
170
170
  };
171
171
  if (this.componentName)
@@ -1,4 +1,12 @@
1
- export declare const VERSION = "0.1.89";
1
+ export declare const VERSION = "0.1.92";
2
+ export declare function normalizeVeryfrontVersion(version: string | undefined): string | undefined;
3
+ export declare function resolveRuntimeVersion(options?: {
4
+ veryfrontVersion?: string;
5
+ releaseVersion?: string;
6
+ denoVersion?: string;
7
+ fallbackVersion?: string;
8
+ }): string;
9
+ export declare const RUNTIME_VERSION: string;
2
10
  export declare const SERVER_START_TIME: number;
3
11
  export interface BuildVersion {
4
12
  framework: string;
@@ -1 +1 @@
1
- {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/src/utils/version.ts"],"names":[],"mappings":"AAEA,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC,eAAO,MAAM,iBAAiB,EAAE,MAAmB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,YAAY,CAM1E"}
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../../../src/src/utils/version.ts"],"names":[],"mappings":"AAKA,eAAO,MAAM,OAAO,WAAW,CAAC;AAEhC,wBAAgB,yBAAyB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,GAAG,MAAM,GAAG,SAAS,CAGzF;AAUD,wBAAgB,qBAAqB,CAAC,OAAO,GAAE;IAC7C,gBAAgB,CAAC,EAAE,MAAM,CAAC;IAC1B,cAAc,CAAC,EAAE,MAAM,CAAC;IACxB,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,eAAe,CAAC,EAAE,MAAM,CAAC;CACrB,GAAG,MAAM,CAKd;AAED,eAAO,MAAM,eAAe,QAK1B,CAAC;AAEH,eAAO,MAAM,iBAAiB,EAAE,MAAmB,CAAC;AAEpD,MAAM,WAAW,YAAY;IAC3B,SAAS,EAAE,MAAM,CAAC;IAClB,WAAW,EAAE,MAAM,CAAC;IACpB,cAAc,CAAC,EAAE,MAAM,CAAC;CACzB;AAED,wBAAgB,kBAAkB,CAAC,gBAAgB,CAAC,EAAE,MAAM,GAAG,YAAY,CAM1E"}
@@ -1,10 +1,37 @@
1
+ import denoConfig from "../../deno.js";
2
+ import { getEnv } from "../platform/compat/process.js";
1
3
  // Keep in sync with deno.json version.
2
4
  // scripts/release.ts updates this constant during releases.
3
- export const VERSION = "0.1.89";
5
+ export const VERSION = "0.1.92";
6
+ export function normalizeVeryfrontVersion(version) {
7
+ if (!version)
8
+ return undefined;
9
+ return version.replace(/^v(?=\d)/, "");
10
+ }
11
+ function getVersionEnv(name) {
12
+ try {
13
+ return getEnv(name);
14
+ }
15
+ catch {
16
+ return undefined;
17
+ }
18
+ }
19
+ export function resolveRuntimeVersion(options = {}) {
20
+ return normalizeVeryfrontVersion(options.veryfrontVersion ?? options.releaseVersion) ??
21
+ normalizeVeryfrontVersion(options.denoVersion) ??
22
+ options.fallbackVersion ??
23
+ VERSION;
24
+ }
25
+ export const RUNTIME_VERSION = resolveRuntimeVersion({
26
+ veryfrontVersion: getVersionEnv("VERYFRONT_VERSION"),
27
+ releaseVersion: getVersionEnv("RELEASE_VERSION"),
28
+ denoVersion: typeof denoConfig.version === "string" ? denoConfig.version : undefined,
29
+ fallbackVersion: VERSION,
30
+ });
4
31
  export const SERVER_START_TIME = Date.now();
5
32
  export function createBuildVersion(projectUpdatedAt) {
6
33
  return {
7
- framework: VERSION,
34
+ framework: RUNTIME_VERSION,
8
35
  serverStart: SERVER_START_TIME,
9
36
  projectUpdated: projectUpdatedAt,
10
37
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "veryfront",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "description": "The simplest way to build AI-powered apps",
5
5
  "keywords": [
6
6
  "react",
package/src/deno.js CHANGED
@@ -1,6 +1,6 @@
1
1
  export default {
2
2
  "name": "veryfront",
3
- "version": "0.1.90",
3
+ "version": "0.1.92",
4
4
  "license": "Apache-2.0",
5
5
  "nodeModulesDir": "auto",
6
6
  "exclude": [
@@ -188,7 +188,7 @@ function readEnvSnapshot(): EnvironmentConfig {
188
188
  ? parseNumber(v8MaxOldSpaceSizeRaw, 0) || undefined
189
189
  : undefined,
190
190
 
191
- veryfrontVersion: getEnv("VERYFRONT_VERSION") || undefined,
191
+ veryfrontVersion: getEnv("VERYFRONT_VERSION") || getEnv("RELEASE_VERSION") || undefined,
192
192
  };
193
193
  }
194
194
 
@@ -115,7 +115,7 @@ const BaseJobSchema = z.object({
115
115
  id: z.string().uuid(),
116
116
  project_id: z.string().uuid(),
117
117
  environment_id: z.string().uuid().nullable(),
118
- branch_id: z.string().nullable().optional().default(null),
118
+ branch_id: z.string().nullable().optional(),
119
119
  cron_job_id: z.string().uuid().nullable(),
120
120
  batch_id: z.string().uuid().nullable(),
121
121
  name: z.string(),
@@ -134,7 +134,7 @@ const BaseJobSchema = z.object({
134
134
  });
135
135
 
136
136
  export const JobSchema = BaseJobSchema.extend({
137
- failed_reason: z.string().nullable().optional().default(null),
137
+ failed_reason: z.string().nullable().optional(),
138
138
  kind: JobKindSchema.optional().default(null),
139
139
  failure_detail: z.string().nullable().optional().default(null),
140
140
  result_summary: JobResultSummarySchema.optional().default(null),
@@ -242,7 +242,7 @@ export const CronJobSchema = z.object({
242
242
  id: z.string().uuid(),
243
243
  project_id: z.string().uuid(),
244
244
  environment_id: z.string().uuid().nullable(),
245
- branch_id: z.string().nullable().optional().default(null),
245
+ branch_id: z.string().nullable().optional(),
246
246
  name: z.string(),
247
247
  status: CronJobStatusSchema,
248
248
  target: z.string(),
@@ -10,7 +10,7 @@ import { loadConfig } from "./config.js";
10
10
  import { initializeInstruments } from "../instruments/index.js";
11
11
  import { MetricsRecorder } from "./recorder.js";
12
12
  import type { MetricsConfig, MetricsInstruments, OpenTelemetryAPI, RuntimeState } from "./types.js";
13
- import { VERSION } from "../../utils/version.js";
13
+ import { RUNTIME_VERSION } from "../../utils/version.js";
14
14
 
15
15
  const logger = serverLogger.component("metrics");
16
16
 
@@ -80,7 +80,7 @@ export class MetricsManager {
80
80
 
81
81
  try {
82
82
  this.api = await import("@opentelemetry/api");
83
- this.meter = this.api.metrics.getMeter(finalConfig.prefix, VERSION);
83
+ this.meter = this.api.metrics.getMeter(finalConfig.prefix, RUNTIME_VERSION);
84
84
 
85
85
  this.instruments = initializeInstruments(this.meter, finalConfig, this.runtimeState);
86
86
  this.recorder.instruments = this.instruments;
@@ -13,7 +13,7 @@ import * as dntShim from "../../../_dnt.shims.js";
13
13
 
14
14
  import { getOtelTracingConfig } from "../../config/env.js";
15
15
  import { serverLogger } from "../../utils/index.js";
16
- import { VERSION } from "../../utils/version.js";
16
+ import { RUNTIME_VERSION } from "../../utils/version.js";
17
17
 
18
18
  const logger = serverLogger.component("otel");
19
19
 
@@ -118,7 +118,7 @@ export async function initializeOTLP(): Promise<void> {
118
118
 
119
119
  const resource = new Resource({
120
120
  [ATTR_SERVICE_NAME]: config.serviceName,
121
- [ATTR_SERVICE_VERSION]: VERSION,
121
+ [ATTR_SERVICE_VERSION]: RUNTIME_VERSION,
122
122
  });
123
123
 
124
124
  const endpointBase = config.endpoint.replace(/\/$/, "");
@@ -350,10 +350,10 @@ export class VeryfrontFSAdapter implements FSAdapter {
350
350
  sourceFilesWithContent: fileSummary.sourceFilesWithContent,
351
351
  });
352
352
 
353
- // Trigger CSS pre-generation after the initial file snapshot is ready.
354
- // This keeps stylesheet generation off the first styles request for both
355
- // preview branches and published content.
356
- if (fileSummary.sourceFilesWithContent > 0) {
353
+ // Trigger CSS pre-generation after the initial file snapshot is ready for
354
+ // published contexts. Branch previews should first try remote metadata
355
+ // recovery on cold starts instead of repopulating the prepared cache here.
356
+ if (fileSummary.sourceFilesWithContent > 0 && this.shouldBackgroundPregenerateStyles()) {
357
357
  this.triggerCSSPregeneration(files).catch(() => {
358
358
  // Error already logged in triggerCSSPregeneration
359
359
  });
@@ -403,6 +403,13 @@ export class VeryfrontFSAdapter implements FSAdapter {
403
403
  return isPrefixBeingInvalidated(prefix);
404
404
  }
405
405
 
406
+ private shouldBackgroundPregenerateStyles(): boolean {
407
+ // Branch previews should recover the last registered stylesheet artifact on
408
+ // cold starts before rebuilding CSS locally. Live edit pokes still
409
+ // pregenerate through the WebSocket path after branch content changes.
410
+ return this.contentContext?.sourceType !== "branch";
411
+ }
412
+
406
413
  private scheduleFileListWarmup(reason: string, cacheKey?: string): void {
407
414
  if (!this.initialized || !this.contentContext) return;
408
415
 
@@ -446,7 +453,7 @@ export class VeryfrontFSAdapter implements FSAdapter {
446
453
  await this.cache.setAsync(effectiveCacheKey, files);
447
454
  const fileSummary = summarizeFileList(files);
448
455
 
449
- if (fileSummary.sourceFilesWithContent > 0) {
456
+ if (fileSummary.sourceFilesWithContent > 0 && this.shouldBackgroundPregenerateStyles()) {
450
457
  this.triggerCSSPregeneration(files).catch(() => {
451
458
  // Error already logged in triggerCSSPregeneration
452
459
  });
@@ -3,7 +3,10 @@ import {
3
3
  type FileDetail,
4
4
  type FileListResult,
5
5
  type ListFilesOptions,
6
+ type ProjectStyleArtifactResolution,
7
+ type ResolveStyleArtifactInput,
6
8
  type TokenProvider,
9
+ type UpsertStyleArtifactInput,
7
10
  VeryfrontAPIOperations,
8
11
  } from "./operations.js";
9
12
  import { API_CLIENT_ERROR, type VeryfrontAPIConfig } from "./types.js";
@@ -340,6 +343,20 @@ export class VeryfrontApiClient {
340
343
  return this.operations.lookupProjectByDomain(domain);
341
344
  }
342
345
 
346
+ resolveStyleArtifact(
347
+ input: ResolveStyleArtifactInput,
348
+ projectRef = this.getProjectSlug()!,
349
+ ): Promise<ProjectStyleArtifactResolution> {
350
+ return this.operations.resolveStyleArtifact(projectRef, input);
351
+ }
352
+
353
+ upsertStyleArtifact(
354
+ input: UpsertStyleArtifactInput,
355
+ projectRef = this.getProjectSlug()!,
356
+ ): Promise<ProjectStyleArtifactResolution> {
357
+ return this.operations.upsertStyleArtifact(projectRef, input);
358
+ }
359
+
343
360
  // =============================================================================
344
361
  // Adapter Convenience Methods
345
362
  // =============================================================================
@@ -9,6 +9,10 @@ export {
9
9
  type FileDetail,
10
10
  type FileListResult,
11
11
  type ListFilesOptions,
12
+ type ProjectStyleArtifactResolution,
13
+ type ResolveStyleArtifactInput,
14
+ type StyleArtifactSelector,
15
+ type UpsertStyleArtifactInput,
12
16
  VeryfrontAPIOperations,
13
17
  } from "./operations.js";
14
18
  export { type RequestOptions, requestWithRetry, type RetryConfig } from "./retry-handler.js";
@@ -39,4 +43,6 @@ export {
39
43
  ProjectSchema,
40
44
  ReleaseFileDetailSchema,
41
45
  ReleaseFileListItemSchema,
46
+ type StyleArtifactResolveResponse,
47
+ StyleArtifactResolveResponseSchema,
42
48
  } from "./schemas/index.js";
@@ -1,6 +1,6 @@
1
1
  import { logger as baseLogger } from "../../../utils/index.js";
2
2
  import { z } from "zod";
3
- import { requestWithRetry, type RetryConfig } from "./retry-handler.js";
3
+ import { type RequestOptions, requestWithRetry, type RetryConfig } from "./retry-handler.js";
4
4
  import { API_CLIENT_ERROR } from "./types.js";
5
5
  import {
6
6
  BranchFileDetailSchema,
@@ -15,6 +15,7 @@ import {
15
15
  type ProjectFile,
16
16
  ProjectSchema,
17
17
  ReleaseFileDetailSchema,
18
+ StyleArtifactResolveResponseSchema,
18
19
  } from "./schemas/index.js";
19
20
  import { withSpan } from "../../../observability/tracing/otlp-setup.js";
20
21
  import { SpanNames } from "../../../observability/tracing/span-names.js";
@@ -53,6 +54,32 @@ export interface FileDetail {
53
54
  release_version?: string | null;
54
55
  }
55
56
 
57
+ export interface StyleArtifactSelector {
58
+ branch?: string;
59
+ environmentName?: string;
60
+ releaseId?: string;
61
+ }
62
+
63
+ export interface ResolveStyleArtifactInput extends StyleArtifactSelector {
64
+ styleProfileHash: string;
65
+ }
66
+
67
+ export interface UpsertStyleArtifactInput extends ResolveStyleArtifactInput {
68
+ artifactHash: string;
69
+ assetPath?: string;
70
+ contentType?: string;
71
+ etag?: string;
72
+ }
73
+
74
+ export interface ProjectStyleArtifactResolution {
75
+ status: "ready" | "missing";
76
+ artifactHash?: string;
77
+ assetPath?: string;
78
+ etag?: string;
79
+ contentType?: string;
80
+ updatedAt?: string;
81
+ }
82
+
56
83
  function buildListParams(options: ListFilesOptions): URLSearchParams {
57
84
  const { cursor, limit = DEFAULT_PAGE_LIMIT, pattern, sortBy = "updated_at", sortOrder = "desc" } =
58
85
  options;
@@ -81,6 +108,30 @@ function mapProjectFile<T extends ProjectFile>(file: T): ProjectFile {
81
108
  };
82
109
  }
83
110
 
111
+ function buildStyleArtifactParams(input: ResolveStyleArtifactInput): URLSearchParams {
112
+ const params = new URLSearchParams({
113
+ style_profile_hash: input.styleProfileHash,
114
+ });
115
+
116
+ if (input.branch) params.set("branch", input.branch);
117
+ if (input.environmentName) params.set("environment_name", input.environmentName);
118
+ if (input.releaseId) params.set("release_id", input.releaseId);
119
+
120
+ return params;
121
+ }
122
+
123
+ function mapStyleArtifactResolution(raw: unknown): ProjectStyleArtifactResolution {
124
+ const response = StyleArtifactResolveResponseSchema.parse(raw);
125
+ return {
126
+ status: response.status,
127
+ artifactHash: response.artifact_hash,
128
+ assetPath: response.asset_path,
129
+ etag: response.etag,
130
+ contentType: response.content_type,
131
+ updatedAt: response.updated_at,
132
+ };
133
+ }
134
+
84
135
  async function listAllFiles(
85
136
  list: (cursor?: string) => Promise<FileListResult>,
86
137
  ): Promise<ProjectFile[]> {
@@ -423,11 +474,67 @@ export class VeryfrontAPIOperations {
423
474
  );
424
475
  }
425
476
 
426
- private request(endpoint: string): Promise<unknown> {
477
+ async resolveStyleArtifact(
478
+ projectRef: string,
479
+ input: ResolveStyleArtifactInput,
480
+ ): Promise<ProjectStyleArtifactResolution> {
481
+ const params = buildStyleArtifactParams(input);
482
+ const url = `/projects/${encodeURIComponent(projectRef)}/style-artifacts/current?${params}`;
483
+ logger.debug("resolveStyleArtifact", {
484
+ projectRef,
485
+ branch: input.branch,
486
+ environmentName: input.environmentName,
487
+ releaseId: input.releaseId,
488
+ styleProfileHash: input.styleProfileHash,
489
+ });
490
+
491
+ return mapStyleArtifactResolution(await this.request(url));
492
+ }
493
+
494
+ async upsertStyleArtifact(
495
+ projectRef: string,
496
+ input: UpsertStyleArtifactInput,
497
+ ): Promise<ProjectStyleArtifactResolution> {
498
+ const url = `/projects/${encodeURIComponent(projectRef)}/style-artifacts/current`;
499
+ logger.debug("upsertStyleArtifact", {
500
+ projectRef,
501
+ branch: input.branch,
502
+ environmentName: input.environmentName,
503
+ releaseId: input.releaseId,
504
+ styleProfileHash: input.styleProfileHash,
505
+ artifactHash: input.artifactHash,
506
+ });
507
+
508
+ return mapStyleArtifactResolution(
509
+ await this.request(url, {
510
+ method: "PUT",
511
+ headers: {
512
+ "Content-Type": "application/json",
513
+ },
514
+ body: JSON.stringify({
515
+ style_profile_hash: input.styleProfileHash,
516
+ branch: input.branch,
517
+ environment_name: input.environmentName,
518
+ release_id: input.releaseId,
519
+ artifact_hash: input.artifactHash,
520
+ asset_path: input.assetPath,
521
+ content_type: input.contentType,
522
+ etag: input.etag,
523
+ }),
524
+ }),
525
+ );
526
+ }
527
+
528
+ private request(endpoint: string, options: RequestOptions = {}): Promise<unknown> {
427
529
  return withSpan(
428
530
  SpanNames.API_REQUEST,
429
531
  () =>
430
- requestWithRetry(`${this.apiBaseUrl}${endpoint}`, this.tokenProvider(), this.retryConfig),
532
+ requestWithRetry(
533
+ `${this.apiBaseUrl}${endpoint}`,
534
+ this.tokenProvider(),
535
+ this.retryConfig,
536
+ options,
537
+ ),
431
538
  { "api.endpoint": endpoint, "api.base_url": this.apiBaseUrl },
432
539
  );
433
540
  }
@@ -147,6 +147,15 @@ export const LookupDomainResponseSchema = z.object({
147
147
  release_id: z.string().uuid().nullable(),
148
148
  });
149
149
 
150
+ export const StyleArtifactResolveResponseSchema = z.object({
151
+ status: z.enum(["ready", "missing"]),
152
+ artifact_hash: z.string().optional(),
153
+ asset_path: z.string().optional(),
154
+ etag: z.string().optional(),
155
+ content_type: z.string().optional(),
156
+ updated_at: z.string().optional(),
157
+ });
158
+
150
159
  export type Project = z.infer<typeof ProjectSchema>;
151
160
  export type ProjectFile = z.infer<typeof ProjectFileSchema>;
152
161
  export type PageInfo = z.infer<typeof PageInfoSchema>;
@@ -165,6 +174,7 @@ export type ListReleaseFilesResponse = z.infer<typeof ListReleaseFilesResponseSc
165
174
  export type ReleaseFileDetail = z.infer<typeof ReleaseFileDetailSchema>;
166
175
 
167
176
  export type LookupDomainResponse = z.infer<typeof LookupDomainResponseSchema>;
177
+ export type StyleArtifactResolveResponse = z.infer<typeof StyleArtifactResolveResponseSchema>;
168
178
 
169
179
  export const API_ENDPOINTS = {
170
180
  listProjects: {
@@ -212,4 +222,10 @@ export const API_ENDPOINTS = {
212
222
  path: "/projects/{domain}",
213
223
  description: "Look up project by custom domain (resolved via project_reference)",
214
224
  },
225
+ resolveStyleArtifact: {
226
+ method: "GET" as const,
227
+ path: "/projects/{projectRef}/style-artifacts/current",
228
+ description:
229
+ "Resolve metadata for the latest ready style artifact for a branch, environment, or release selector",
230
+ },
215
231
  } as const;
@@ -35,4 +35,6 @@ export {
35
35
  ReleaseFileDetailSchema,
36
36
  type ReleaseFileListItem,
37
37
  ReleaseFileListItemSchema,
38
+ type StyleArtifactResolveResponse,
39
+ StyleArtifactResolveResponseSchema,
38
40
  } from "./api.schema.js";
@@ -1,10 +1,8 @@
1
- // Import version from root deno.json (the source of truth)
2
1
  import * as dntShim from "../../_dnt.shims.js";
3
-
4
- import denoConfig from "../../deno.js";
5
2
  import { getEnv } from "./env.js";
6
3
  import { getTraceContext } from "./tracing.js";
7
4
  import { AsyncLocalStorage } from "node:async_hooks";
5
+ import { PROXY_RUNTIME_VERSION } from "./version.js";
8
6
 
9
7
  // NOTE: Formatting utilities are INLINED below instead of imported from ../utils/logger/core.ts
10
8
  // because the proxy Docker build only copies src/proxy/ and has no access to src/utils/.
@@ -45,10 +43,6 @@ function getProxyRequestContext(): ProxyRequestContext | undefined {
45
43
  return requestContextStore.getStore();
46
44
  }
47
45
 
48
- // Get version from environment variable or root deno.json
49
- const VERYFRONT_VERSION: string = getEnv("VERYFRONT_VERSION") ??
50
- (typeof denoConfig.version === "string" ? denoConfig.version : "0.0.0");
51
-
52
46
  export type LogLevel = "debug" | "info" | "warn" | "error";
53
47
 
54
48
  const LOG_LEVEL_ORDER: Record<LogLevel, number> = {
@@ -295,7 +289,7 @@ class ProxyLogger {
295
289
  timestamp: new Date().toISOString(),
296
290
  level,
297
291
  service: "proxy",
298
- veryfrontVersion: VERYFRONT_VERSION,
292
+ veryfrontVersion: PROXY_RUNTIME_VERSION,
299
293
  message,
300
294
  ...(traceCtx.traceId && { traceId: traceCtx.traceId, spanId: traceCtx.spanId }),
301
295
  // Include request context fields at top level (like renderer logs)