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.
- package/esm/cli/commands/styles/command-help.d.ts +3 -0
- package/esm/cli/commands/styles/command-help.d.ts.map +1 -0
- package/esm/cli/commands/styles/command-help.js +19 -0
- package/esm/cli/commands/styles/command.d.ts +3 -0
- package/esm/cli/commands/styles/command.d.ts.map +1 -0
- package/esm/cli/commands/styles/command.js +198 -0
- package/esm/cli/commands/styles/handler.d.ts +24 -0
- package/esm/cli/commands/styles/handler.d.ts.map +1 -0
- package/esm/cli/commands/styles/handler.js +17 -0
- package/esm/cli/help/command-definitions.d.ts.map +1 -1
- package/esm/cli/help/command-definitions.js +2 -0
- package/esm/cli/router.d.ts.map +1 -1
- package/esm/cli/router.js +2 -0
- package/esm/deno.d.ts +1 -0
- package/esm/deno.js +2 -1
- package/esm/src/html/styles-builder/css-pregeneration.d.ts +9 -0
- package/esm/src/html/styles-builder/css-pregeneration.d.ts.map +1 -1
- package/esm/src/html/styles-builder/css-pregeneration.js +26 -15
- package/esm/src/jobs/schemas.d.ts +40 -40
- package/esm/src/platform/adapters/fs/veryfront/adapter.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/adapter.js +15 -3
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts +2 -0
- package/esm/src/platform/adapters/fs/veryfront/types.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts +5 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +10 -2
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts +2 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/client.js +3 -0
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/index.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts +11 -2
- package/esm/src/platform/adapters/veryfront-api-client/operations.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/operations.js +30 -0
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts +14 -3
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.d.ts.map +1 -1
- package/esm/src/platform/adapters/veryfront-api-client/schemas/api.schema.js +8 -1
- package/esm/src/rendering/styles.d.ts +9 -0
- package/esm/src/rendering/styles.d.ts.map +1 -0
- package/esm/src/rendering/styles.js +8 -0
- package/esm/src/sandbox/index.d.ts +1 -1
- package/esm/src/sandbox/index.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.d.ts +14 -3
- package/esm/src/sandbox/sandbox.d.ts.map +1 -1
- package/esm/src/sandbox/sandbox.js +20 -6
- package/esm/src/security/http/base-handler.d.ts.map +1 -1
- package/esm/src/security/http/base-handler.js +5 -2
- package/esm/src/server/context/request-context.d.ts.map +1 -1
- package/esm/src/server/context/request-context.js +4 -2
- package/esm/src/server/handlers/dev/scripts/hmr-scripts.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/scripts/hmr-scripts.js +91 -4
- package/esm/src/server/handlers/dev/styles-css.handler.d.ts +2 -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 +23 -0
- package/esm/src/server/handlers/preview/hmr-message-router.d.ts +2 -1
- package/esm/src/server/handlers/preview/hmr-message-router.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/hmr-message-router.js +15 -5
- package/esm/src/server/handlers/preview/hmr.handler.js +1 -1
- package/esm/src/server/handlers/preview/markdown-preview.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/preview/markdown-preview.handler.js +4 -2
- package/esm/src/server/handlers/request/css.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/css.handler.js +4 -2
- package/esm/src/server/handlers/request/ssr/ssr.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/ssr/ssr.handler.js +4 -2
- package/esm/src/server/reload-notifier.d.ts +3 -1
- package/esm/src/server/reload-notifier.d.ts.map +1 -1
- package/esm/src/utils/version.d.ts +1 -1
- package/esm/src/utils/version.js +1 -1
- package/package.json +1 -1
- package/src/cli/commands/styles/command-help.ts +21 -0
- package/src/cli/commands/styles/command.ts +296 -0
- package/src/cli/commands/styles/handler.ts +23 -0
- package/src/cli/help/command-definitions.ts +2 -0
- package/src/cli/router.ts +2 -0
- package/src/deno.js +2 -1
- package/src/src/html/styles-builder/css-pregeneration.ts +57 -29
- package/src/src/platform/adapters/fs/veryfront/adapter.ts +18 -4
- package/src/src/platform/adapters/fs/veryfront/types.ts +2 -0
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +18 -3
- package/src/src/platform/adapters/veryfront-api-client/client.ts +8 -0
- package/src/src/platform/adapters/veryfront-api-client/index.ts +1 -0
- package/src/src/platform/adapters/veryfront-api-client/operations.ts +48 -2
- package/src/src/platform/adapters/veryfront-api-client/schemas/api.schema.ts +9 -1
- package/src/src/rendering/styles.ts +15 -0
- package/src/src/sandbox/index.ts +1 -0
- package/src/src/sandbox/sandbox.ts +33 -6
- package/src/src/security/http/base-handler.ts +5 -2
- package/src/src/server/context/request-context.ts +4 -2
- package/src/src/server/handlers/dev/scripts/hmr-scripts.ts +93 -4
- package/src/src/server/handlers/dev/styles-css.handler.ts +31 -0
- package/src/src/server/handlers/preview/hmr-message-router.ts +15 -5
- package/src/src/server/handlers/preview/hmr.handler.ts +1 -1
- package/src/src/server/handlers/preview/markdown-preview.handler.ts +4 -2
- package/src/src/server/handlers/request/css.handler.ts +4 -2
- package/src/src/server/handlers/request/ssr/ssr.handler.ts +4 -2
- package/src/src/server/reload-notifier.ts +3 -1
- package/src/src/utils/version.ts +1 -1
|
@@ -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
|
-
|
|
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
|
);
|
|
@@ -148,11 +148,13 @@ export const LookupDomainResponseSchema = z.object({
|
|
|
148
148
|
});
|
|
149
149
|
|
|
150
150
|
export const StyleArtifactResolveResponseSchema = z.object({
|
|
151
|
-
status: z.enum(["ready", "missing"]),
|
|
151
|
+
status: z.enum(["ready", "missing", "building", "failed"]),
|
|
152
152
|
artifact_hash: z.string().optional(),
|
|
153
153
|
asset_path: z.string().optional(),
|
|
154
154
|
etag: z.string().optional(),
|
|
155
155
|
content_type: z.string().optional(),
|
|
156
|
+
build_job_id: z.string().uuid().optional(),
|
|
157
|
+
failure_reason: z.string().optional(),
|
|
156
158
|
updated_at: z.string().optional(),
|
|
157
159
|
});
|
|
158
160
|
|
|
@@ -228,4 +230,10 @@ export const API_ENDPOINTS = {
|
|
|
228
230
|
description:
|
|
229
231
|
"Resolve metadata for the latest ready style artifact for a branch, environment, or release selector",
|
|
230
232
|
},
|
|
233
|
+
ensureStyleArtifactBuild: {
|
|
234
|
+
method: "POST" as const,
|
|
235
|
+
path: "/projects/{projectRef}/style-artifacts/current/builds",
|
|
236
|
+
description:
|
|
237
|
+
"Ensure a background style artifact build exists for a branch, environment, or release selector",
|
|
238
|
+
},
|
|
231
239
|
} as const;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Public style artifact helpers used by CLI and worker entrypoints.
|
|
3
|
+
*
|
|
4
|
+
* @module rendering/styles
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
export {
|
|
8
|
+
buildPreparedCSSArtifactFromFiles,
|
|
9
|
+
type PreparedCSSArtifactBuildResult,
|
|
10
|
+
} from "../html/styles-builder/css-pregeneration.js";
|
|
11
|
+
export {
|
|
12
|
+
createStyleScopeProfile,
|
|
13
|
+
type StyleScopeProfile,
|
|
14
|
+
} from "../html/styles-builder/style-scope-profile.js";
|
|
15
|
+
export { resolveStyleContentVersion } from "../html/styles-builder/content-version.js";
|
package/src/src/sandbox/index.ts
CHANGED
|
@@ -19,6 +19,16 @@ import {
|
|
|
19
19
|
import { getVeryfrontCloudAuthToken } from "../platform/cloud/resolver.js";
|
|
20
20
|
import { getHostEnv } from "../platform/compat/process.js";
|
|
21
21
|
|
|
22
|
+
/** Options for command execution: working directory, timeout, and environment variables. */
|
|
23
|
+
export interface ExecOptions {
|
|
24
|
+
/** Working directory for the command. */
|
|
25
|
+
cwd?: string;
|
|
26
|
+
/** Timeout in seconds for the command. */
|
|
27
|
+
timeout_seconds?: number;
|
|
28
|
+
/** Additional environment variables for the command. */
|
|
29
|
+
env?: Record<string, string>;
|
|
30
|
+
}
|
|
31
|
+
|
|
22
32
|
/** Options for creating a sandbox session. */
|
|
23
33
|
export interface SandboxOptions {
|
|
24
34
|
/** Base URL of the Veryfront API. Defaults to VERYFRONT_API_URL env. */
|
|
@@ -242,12 +252,12 @@ export class Sandbox {
|
|
|
242
252
|
}
|
|
243
253
|
|
|
244
254
|
/** Execute a bash command in the sandbox and return buffered result. */
|
|
245
|
-
async executeCommand(command: string): Promise<ExecResult> {
|
|
255
|
+
async executeCommand(command: string, options?: ExecOptions): Promise<ExecResult> {
|
|
246
256
|
let stdout = "";
|
|
247
257
|
let stderr = "";
|
|
248
258
|
let exitCode = 1;
|
|
249
259
|
|
|
250
|
-
for await (const event of this.executeStream(command)) {
|
|
260
|
+
for await (const event of this.executeStream(command, options)) {
|
|
251
261
|
switch (event.type) {
|
|
252
262
|
case "stdout":
|
|
253
263
|
stdout += event.data ?? "";
|
|
@@ -265,14 +275,14 @@ export class Sandbox {
|
|
|
265
275
|
}
|
|
266
276
|
|
|
267
277
|
/** Execute a bash command with streaming output (NDJSON). */
|
|
268
|
-
async *executeStream(command: string): AsyncGenerator<ExecStreamEvent> {
|
|
278
|
+
async *executeStream(command: string, options?: ExecOptions): AsyncGenerator<ExecStreamEvent> {
|
|
269
279
|
const res = await dntShim.fetch(`${this.endpoint}/exec`, {
|
|
270
280
|
method: "POST",
|
|
271
281
|
headers: {
|
|
272
282
|
Authorization: `Bearer ${this.authToken}`,
|
|
273
283
|
"Content-Type": "application/json",
|
|
274
284
|
},
|
|
275
|
-
body: JSON.stringify({ command }),
|
|
285
|
+
body: JSON.stringify({ command, ...options }),
|
|
276
286
|
});
|
|
277
287
|
|
|
278
288
|
if (!res.ok) {
|
|
@@ -343,14 +353,14 @@ export class Sandbox {
|
|
|
343
353
|
}
|
|
344
354
|
|
|
345
355
|
/** Start an async command job in the sandbox. */
|
|
346
|
-
async startCommandJob(command: string): Promise<CommandJob> {
|
|
356
|
+
async startCommandJob(command: string, options?: ExecOptions): Promise<CommandJob> {
|
|
347
357
|
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs`, {
|
|
348
358
|
method: "POST",
|
|
349
359
|
headers: {
|
|
350
360
|
Authorization: `Bearer ${this.authToken}`,
|
|
351
361
|
"Content-Type": "application/json",
|
|
352
362
|
},
|
|
353
|
-
body: JSON.stringify({ command }),
|
|
363
|
+
body: JSON.stringify({ command, ...options }),
|
|
354
364
|
});
|
|
355
365
|
|
|
356
366
|
if (!res.ok) {
|
|
@@ -399,6 +409,23 @@ export class Sandbox {
|
|
|
399
409
|
};
|
|
400
410
|
}
|
|
401
411
|
|
|
412
|
+
/** List all command jobs in the sandbox. */
|
|
413
|
+
async listCommandJobs(): Promise<CommandJob[]> {
|
|
414
|
+
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs`, {
|
|
415
|
+
headers: { Authorization: `Bearer ${this.authToken}` },
|
|
416
|
+
});
|
|
417
|
+
|
|
418
|
+
if (!res.ok) {
|
|
419
|
+
throw REQUEST_ERROR.create({
|
|
420
|
+
detail: `List command jobs failed: ${res.status} ${await res.text()}`,
|
|
421
|
+
});
|
|
422
|
+
}
|
|
423
|
+
|
|
424
|
+
const json = await res.json();
|
|
425
|
+
const jobs = Array.isArray(json) ? json : (json.jobs ?? []);
|
|
426
|
+
return jobs.map((j: Record<string, unknown>) => Sandbox.mapCommandJob(j));
|
|
427
|
+
}
|
|
428
|
+
|
|
402
429
|
/** Cancel an async command job. */
|
|
403
430
|
async cancelCommandJob(jobId: string): Promise<CommandJob> {
|
|
404
431
|
const res = await dntShim.fetch(`${this.endpoint}/exec/jobs/${jobId}/cancel`, {
|
|
@@ -7,6 +7,7 @@ import type {
|
|
|
7
7
|
RoutePattern,
|
|
8
8
|
} from "../../types/index.js";
|
|
9
9
|
import { runWithCacheBatching } from "../../cache/request-cache-batcher.js";
|
|
10
|
+
import { getHostEnv } from "../../platform/compat/process.js";
|
|
10
11
|
import { serverLogger } from "../../utils/index.js";
|
|
11
12
|
import { ResponseBuilder } from "./response/index.js";
|
|
12
13
|
|
|
@@ -78,7 +79,7 @@ export abstract class BaseHandler implements Handler {
|
|
|
78
79
|
}
|
|
79
80
|
|
|
80
81
|
protected logDebug(message: string, extra?: Record<string, unknown>, ctx?: HandlerContext): void {
|
|
81
|
-
if (!ctx?.debug && !
|
|
82
|
+
if (!ctx?.debug && !getHostEnv("VERYFRONT_DEBUG")) return;
|
|
82
83
|
serverLogger.debug(`[${this.metadata.name}] ${message}`, extra ?? undefined);
|
|
83
84
|
}
|
|
84
85
|
|
|
@@ -108,7 +109,9 @@ export abstract class BaseHandler implements Handler {
|
|
|
108
109
|
fn: () => Promise<T>,
|
|
109
110
|
options: { requireToken?: boolean } = {},
|
|
110
111
|
): Promise<T> {
|
|
111
|
-
|
|
112
|
+
// Framework-owned token: bypass project env overlay so proxy mode works
|
|
113
|
+
// when a remote project overlay is active.
|
|
114
|
+
const effectiveToken = ctx.proxyToken || getHostEnv("VERYFRONT_API_TOKEN") || "";
|
|
112
115
|
const fsWrapper = ctx.adapter.fs as {
|
|
113
116
|
setRequestToken?: (t: string) => void;
|
|
114
117
|
setRequestBranch?: (b: string | null) => void;
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import * as dntShim from "../../../_dnt.shims.js";
|
|
2
|
-
import {
|
|
2
|
+
import { getHostEnv } from "../../platform/compat/process.js";
|
|
3
3
|
import { parseProjectDomain } from "../utils/domain-parser.js";
|
|
4
4
|
|
|
5
5
|
export interface RequestContext {
|
|
@@ -28,7 +28,9 @@ export function createRequestContext(req: dntShim.Request): RequestContext {
|
|
|
28
28
|
: "production";
|
|
29
29
|
|
|
30
30
|
return {
|
|
31
|
-
|
|
31
|
+
// Framework-owned token: bypass project env overlay so proxy mode works
|
|
32
|
+
// when a remote project overlay is active.
|
|
33
|
+
token: req.headers.get("x-token") ?? getHostEnv("VERYFRONT_API_TOKEN") ?? "",
|
|
32
34
|
slug: req.headers.get("x-project-slug") ?? parsed.slug ?? "",
|
|
33
35
|
branch: parsed.branch,
|
|
34
36
|
mode,
|
|
@@ -57,11 +57,94 @@ function getUpdateJSFunction(logPrefix: string): string {
|
|
|
57
57
|
return false;
|
|
58
58
|
}
|
|
59
59
|
|
|
60
|
+
async function swapTailwindStylesheet(nextHref) {
|
|
61
|
+
const current = document.getElementById('vf-tailwind-css');
|
|
62
|
+
if (!(current instanceof HTMLLinkElement) || !nextHref || !current.parentNode) {
|
|
63
|
+
return false;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
const nextUrl = new URL(nextHref, window.location.origin).toString();
|
|
67
|
+
const currentHref = current.getAttribute('href');
|
|
68
|
+
const currentUrl = currentHref ? new URL(currentHref, window.location.href).toString() : '';
|
|
69
|
+
if (currentUrl === nextUrl) {
|
|
70
|
+
return true;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const pending = current.cloneNode(false);
|
|
74
|
+
if (!(pending instanceof HTMLLinkElement)) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
pending.removeAttribute('id');
|
|
79
|
+
pending.setAttribute('data-vf-tailwind-pending', 'true');
|
|
80
|
+
pending.href = nextHref;
|
|
81
|
+
|
|
82
|
+
await new Promise((resolve, reject) => {
|
|
83
|
+
const timeoutId = window.setTimeout(() => {
|
|
84
|
+
cleanup(new Error('stylesheet-timeout'));
|
|
85
|
+
}, 5000);
|
|
86
|
+
|
|
87
|
+
let settled = false;
|
|
88
|
+
|
|
89
|
+
function cleanup(error) {
|
|
90
|
+
if (settled) return;
|
|
91
|
+
settled = true;
|
|
92
|
+
window.clearTimeout(timeoutId);
|
|
93
|
+
pending.removeEventListener('load', onLoad);
|
|
94
|
+
pending.removeEventListener('error', onError);
|
|
95
|
+
|
|
96
|
+
if (error) {
|
|
97
|
+
pending.remove();
|
|
98
|
+
reject(error);
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
pending.id = 'vf-tailwind-css';
|
|
103
|
+
current.remove();
|
|
104
|
+
resolve(true);
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
function onLoad() {
|
|
108
|
+
cleanup(null);
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
function onError() {
|
|
112
|
+
cleanup(new Error('stylesheet-load-failed'));
|
|
113
|
+
}
|
|
114
|
+
|
|
115
|
+
pending.addEventListener('load', onLoad, { once: true });
|
|
116
|
+
pending.addEventListener('error', onError, { once: true });
|
|
117
|
+
current.parentNode.insertBefore(pending, current.nextSibling);
|
|
118
|
+
});
|
|
119
|
+
|
|
120
|
+
return true;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
async function applyStyleUpdate(changedPath, styleHref) {
|
|
124
|
+
if (styleHref) {
|
|
125
|
+
try {
|
|
126
|
+
const swapped = await swapTailwindStylesheet(styleHref);
|
|
127
|
+
if (swapped) {
|
|
128
|
+
${
|
|
129
|
+
logPrefix === "[HMR]"
|
|
130
|
+
? `console.log('${logPrefix} Swapped stylesheet:', styleHref);`
|
|
131
|
+
: `dlog('${logPrefix} Swapped stylesheet:', styleHref);`
|
|
132
|
+
}
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
} catch (error) {
|
|
136
|
+
console.warn('${logPrefix} Failed to swap stylesheet:', error);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
return refreshStylesheets(changedPath) || refreshStylesheets();
|
|
141
|
+
}
|
|
142
|
+
|
|
60
143
|
function getRenderPath() {
|
|
61
144
|
return window.location.pathname + window.location.search + window.location.hash;
|
|
62
145
|
}
|
|
63
146
|
|
|
64
|
-
async function updateJS(path) {
|
|
147
|
+
async function updateJS(path, styleHref) {
|
|
65
148
|
${
|
|
66
149
|
logPrefix === "[HMR]"
|
|
67
150
|
? `console.log('${logPrefix} Updating JS module:', path);`
|
|
@@ -83,7 +166,7 @@ function getUpdateJSFunction(logPrefix: string): string {
|
|
|
83
166
|
}
|
|
84
167
|
|
|
85
168
|
// Refresh Tailwind CSS (new classes may be needed from JS changes)
|
|
86
|
-
|
|
169
|
+
await applyStyleUpdate(path, styleHref);
|
|
87
170
|
|
|
88
171
|
// Re-render the page with fresh modules
|
|
89
172
|
if (window.__veryfrontRenderPage) {
|
|
@@ -271,6 +354,7 @@ function generateHMRClient(opts: HMRScriptOptions): string {
|
|
|
271
354
|
|
|
272
355
|
// Debounce updates to prevent flashing from rapid-fire changes
|
|
273
356
|
let pendingPaths = [];
|
|
357
|
+
let pendingStyleHref = null;
|
|
274
358
|
let updateDebounceTimer = null;
|
|
275
359
|
const UPDATE_DEBOUNCE_MS = 150;
|
|
276
360
|
|
|
@@ -282,7 +366,7 @@ function generateHMRClient(opts: HMRScriptOptions): string {
|
|
|
282
366
|
|
|
283
367
|
// CSS changes: hot-swap stylesheet without full page reload
|
|
284
368
|
if (update.path.endsWith('.css')) {
|
|
285
|
-
const didRefresh =
|
|
369
|
+
const didRefresh = await applyStyleUpdate(update.path, update.styleHref);
|
|
286
370
|
if (!didRefresh) {
|
|
287
371
|
notifyStudioAndReload('css-update-no-stylesheet');
|
|
288
372
|
return;
|
|
@@ -293,12 +377,17 @@ function generateHMRClient(opts: HMRScriptOptions): string {
|
|
|
293
377
|
|
|
294
378
|
// Debounce JS updates — batch rapid updates into single re-render
|
|
295
379
|
pendingPaths.push(update.path);
|
|
380
|
+
if (typeof update.styleHref === 'string') {
|
|
381
|
+
pendingStyleHref = update.styleHref;
|
|
382
|
+
}
|
|
296
383
|
|
|
297
384
|
if (updateDebounceTimer) clearTimeout(updateDebounceTimer);
|
|
298
385
|
|
|
299
386
|
updateDebounceTimer = setTimeout(async () => {
|
|
300
387
|
const paths = pendingPaths;
|
|
388
|
+
const styleHref = pendingStyleHref;
|
|
301
389
|
pendingPaths = [];
|
|
390
|
+
pendingStyleHref = null;
|
|
302
391
|
updateDebounceTimer = null;
|
|
303
392
|
|
|
304
393
|
if (paths.length > 1) {
|
|
@@ -306,7 +395,7 @@ function generateHMRClient(opts: HMRScriptOptions): string {
|
|
|
306
395
|
}
|
|
307
396
|
|
|
308
397
|
// Single re-render handles all paths (server propagates timestamps to all imports)
|
|
309
|
-
if (paths.length > 0) await updateJS(paths[0]);
|
|
398
|
+
if (paths.length > 0) await updateJS(paths[0], styleHref);
|
|
310
399
|
}, UPDATE_DEBOUNCE_MS);
|
|
311
400
|
}
|
|
312
401
|
${getUpdateJSFunction(logPrefix)}
|
|
@@ -29,6 +29,7 @@ import { createStyleScopeProfile } from "../../../html/styles-builder/style-scop
|
|
|
29
29
|
import { serverLogger } from "../../../utils/index.js";
|
|
30
30
|
import type { ResolvedContentContext } from "../../../platform/adapters/fs/veryfront/types.js";
|
|
31
31
|
import type {
|
|
32
|
+
EnsureStyleArtifactBuildInput,
|
|
32
33
|
ResolveStyleArtifactInput,
|
|
33
34
|
VeryfrontApiClient,
|
|
34
35
|
} from "../../../platform/adapters/veryfront-api-client/index.js";
|
|
@@ -353,6 +354,9 @@ body::before {
|
|
|
353
354
|
});
|
|
354
355
|
|
|
355
356
|
if (resolved.status !== "ready" || !resolved.artifactHash) {
|
|
357
|
+
if (resolved.status !== "building") {
|
|
358
|
+
await this.ensureRemotePreparedCSSBuild(client, selector, styleProfileHash);
|
|
359
|
+
}
|
|
356
360
|
return undefined;
|
|
357
361
|
}
|
|
358
362
|
|
|
@@ -415,4 +419,31 @@ body::before {
|
|
|
415
419
|
});
|
|
416
420
|
}
|
|
417
421
|
}
|
|
422
|
+
|
|
423
|
+
private shouldEnsureRemoteStyleArtifactBuild(selector: StyleArtifactSelectorContext): boolean {
|
|
424
|
+
return Boolean(selector.environmentName || selector.releaseId);
|
|
425
|
+
}
|
|
426
|
+
|
|
427
|
+
private async ensureRemotePreparedCSSBuild(
|
|
428
|
+
client: VeryfrontApiClient,
|
|
429
|
+
selector: StyleArtifactSelectorContext,
|
|
430
|
+
styleProfileHash: string,
|
|
431
|
+
): Promise<void> {
|
|
432
|
+
if (!this.shouldEnsureRemoteStyleArtifactBuild(selector)) return;
|
|
433
|
+
|
|
434
|
+
try {
|
|
435
|
+
await client.ensureStyleArtifactBuild(
|
|
436
|
+
{
|
|
437
|
+
...selector,
|
|
438
|
+
styleProfileHash,
|
|
439
|
+
} satisfies EnsureStyleArtifactBuildInput,
|
|
440
|
+
);
|
|
441
|
+
} catch (error) {
|
|
442
|
+
logger.debug("Failed to ensure remote prepared CSS build", {
|
|
443
|
+
selector,
|
|
444
|
+
styleProfileHash,
|
|
445
|
+
error: error instanceof Error ? error.message : String(error),
|
|
446
|
+
});
|
|
447
|
+
}
|
|
448
|
+
}
|
|
418
449
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { serverLogger } from "../../../utils/index.js";
|
|
2
|
+
import type { ReloadProjectInfo } from "../../reload-notifier.js";
|
|
2
3
|
import { getClientCount, getOpenSockets } from "./hmr-client-manager.js";
|
|
3
4
|
|
|
4
5
|
const logger = serverLogger.component("hmr-handler");
|
|
@@ -19,6 +20,13 @@ export function getMetrics(): { clients: number } & HMRMetrics {
|
|
|
19
20
|
return { clients: getClientCount(), ...metrics };
|
|
20
21
|
}
|
|
21
22
|
|
|
23
|
+
function buildStyleUpdatePayload(project?: ReloadProjectInfo): Record<string, string> {
|
|
24
|
+
const payload: Record<string, string> = {};
|
|
25
|
+
if (project?.styleAssetPath) payload.styleHref = project.styleAssetPath;
|
|
26
|
+
if (project?.styleArtifactHash) payload.styleHash = project.styleArtifactHash;
|
|
27
|
+
return payload;
|
|
28
|
+
}
|
|
29
|
+
|
|
22
30
|
function requiresFullReload(path: string): boolean {
|
|
23
31
|
const ext = path.split(".").pop()?.toLowerCase();
|
|
24
32
|
return ext === "mdx" || ext === "md" || path.includes("veryfront.config");
|
|
@@ -28,11 +36,12 @@ function requiresFullReload(path: string): boolean {
|
|
|
28
36
|
* Broadcast update to all connected HMR clients, optionally filtered by projectSlug.
|
|
29
37
|
* No server-side debounce here — ReloadNotifier already debounces (300ms).
|
|
30
38
|
*/
|
|
31
|
-
export function broadcastUpdate(changedPaths?: string[],
|
|
39
|
+
export function broadcastUpdate(changedPaths?: string[], project?: ReloadProjectInfo): void {
|
|
32
40
|
logger.debug("broadcastUpdate called", {
|
|
33
41
|
changedPaths,
|
|
34
42
|
totalClients: getClientCount(),
|
|
35
|
-
projectSlug,
|
|
43
|
+
projectSlug: project?.projectSlug,
|
|
44
|
+
styleAssetPath: project?.styleAssetPath,
|
|
36
45
|
});
|
|
37
46
|
|
|
38
47
|
const timestamp = Date.now();
|
|
@@ -44,12 +53,13 @@ export function broadcastUpdate(changedPaths?: string[], projectSlug?: string):
|
|
|
44
53
|
|
|
45
54
|
if (needsFullReload) {
|
|
46
55
|
const message = JSON.stringify({ type: "reload", timestamp });
|
|
47
|
-
broadcastMessage(message, projectSlug);
|
|
56
|
+
broadcastMessage(message, project?.projectSlug);
|
|
48
57
|
metrics.messagesForwarded++;
|
|
49
58
|
} else {
|
|
59
|
+
const stylePayload = buildStyleUpdatePayload(project);
|
|
50
60
|
for (const path of changedPaths) {
|
|
51
|
-
const message = JSON.stringify({ type: "update", path, timestamp });
|
|
52
|
-
broadcastMessage(message, projectSlug);
|
|
61
|
+
const message = JSON.stringify({ type: "update", path, timestamp, ...stylePayload });
|
|
62
|
+
broadcastMessage(message, project?.projectSlug);
|
|
53
63
|
metrics.messagesForwarded++;
|
|
54
64
|
}
|
|
55
65
|
}
|
|
@@ -16,7 +16,7 @@ import { HTTP_OK } from "../../../utils/constants/index.js";
|
|
|
16
16
|
import { compileMarkdownRuntime } from "../../../transforms/md/compiler/md-compiler.js";
|
|
17
17
|
import { extract } from "../../../platform/compat/std/front-matter-yaml.js";
|
|
18
18
|
import { isExtendedFSAdapter } from "../../../platform/adapters/fs/wrapper.js";
|
|
19
|
-
import {
|
|
19
|
+
import { getHostEnv } from "../../../platform/compat/process.js";
|
|
20
20
|
import { tryNotFoundFallback } from "../request/ssr/not-found-fallback.js";
|
|
21
21
|
import { generateMarkdownHtml } from "./markdown-html-generator.js";
|
|
22
22
|
import { validatePathSync } from "../../../security/index.js";
|
|
@@ -70,7 +70,9 @@ export class MarkdownPreviewHandler extends BaseHandler {
|
|
|
70
70
|
const hasMultiProjectSupport = isExtendedFSAdapter(fsAdapter) && fsAdapter.isMultiProjectMode();
|
|
71
71
|
|
|
72
72
|
if (ctx.projectSlug && hasMultiProjectSupport) {
|
|
73
|
-
|
|
73
|
+
// Framework-owned token: bypass project env overlay so proxy mode works
|
|
74
|
+
// when a remote project overlay is active.
|
|
75
|
+
const effectiveToken = ctx.proxyToken || getHostEnv("VERYFRONT_API_TOKEN") || "";
|
|
74
76
|
const branch = ctx.parsedDomain?.branch ?? null;
|
|
75
77
|
|
|
76
78
|
return await fsAdapter.runWithContext(
|
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
extractCacheKeyContext,
|
|
11
11
|
runWithCacheKeyContext,
|
|
12
12
|
} from "../../../cache/cache-key-builder.js";
|
|
13
|
-
import {
|
|
13
|
+
import { getHostEnv } from "../../../platform/compat/process.js";
|
|
14
14
|
import { runWithRequestContext } from "../../../platform/adapters/fs/veryfront/multi-project-adapter.js";
|
|
15
15
|
|
|
16
16
|
/** Pattern to match hashed CSS URLs: /_vf/css/[8-char-hash].css */
|
|
@@ -50,7 +50,9 @@ export class CSSHandler extends BaseHandler {
|
|
|
50
50
|
// the distributed API cache backend can't authenticate and silently returns
|
|
51
51
|
// null — causing cross-pod cache misses. Wrap the lookup in request context
|
|
52
52
|
// so the API backend can resolve the token and project.
|
|
53
|
-
|
|
53
|
+
// Framework-owned token: bypass project env overlay so proxy mode works
|
|
54
|
+
// when a remote project overlay is active.
|
|
55
|
+
const effectiveToken = ctx.proxyToken || getHostEnv("VERYFRONT_API_TOKEN") || "";
|
|
54
56
|
const lookup = () =>
|
|
55
57
|
runWithCacheKeyContext(cacheCtx, () =>
|
|
56
58
|
getCSSWithJITFallback(
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
import { PRIORITY_LOW } from "../../../../utils/constants/index.js";
|
|
20
20
|
import { generateNonce } from "../../../../security/http/response/security-handler.js";
|
|
21
21
|
import { isExtendedFSAdapter } from "../../../../platform/adapters/fs/wrapper.js";
|
|
22
|
-
import {
|
|
22
|
+
import { getHostEnv } from "../../../../platform/compat/process.js";
|
|
23
23
|
import { shouldUseNoCacheHeadersFromHandler } from "../../../context/enriched-context.js";
|
|
24
24
|
import { withSpan } from "../../../../observability/tracing/otlp-setup.js";
|
|
25
25
|
import { serverLogger } from "../../../../utils/index.js";
|
|
@@ -117,7 +117,9 @@ export class SSRHandler extends BaseHandler {
|
|
|
117
117
|
if (ctx.projectSlug && isExtended && fsAdapter.isMultiProjectMode()) {
|
|
118
118
|
const prodMode = isProductionMode(ctx, url);
|
|
119
119
|
const branch = ctx.parsedDomain?.branch ?? null;
|
|
120
|
-
|
|
120
|
+
// Framework-owned token: bypass project env overlay so proxy mode works
|
|
121
|
+
// when a remote project overlay is active.
|
|
122
|
+
const effectiveToken = ctx.proxyToken || getHostEnv("VERYFRONT_API_TOKEN") || "";
|
|
121
123
|
|
|
122
124
|
logger.debug("Using multi-project context", {
|
|
123
125
|
projectSlug: ctx.projectSlug,
|
|
@@ -3,13 +3,15 @@ import { serverLogger } from "../utils/index.js";
|
|
|
3
3
|
|
|
4
4
|
const logger = serverLogger.component("reload-notifier");
|
|
5
5
|
|
|
6
|
-
interface ReloadProjectInfo {
|
|
6
|
+
export interface ReloadProjectInfo {
|
|
7
7
|
projectSlug?: string;
|
|
8
8
|
projectId?: string;
|
|
9
9
|
projectDir?: string;
|
|
10
10
|
environment?: "preview" | "production";
|
|
11
11
|
branch?: string | null;
|
|
12
12
|
releaseId?: string | null;
|
|
13
|
+
styleArtifactHash?: string;
|
|
14
|
+
styleAssetPath?: string;
|
|
13
15
|
}
|
|
14
16
|
|
|
15
17
|
type ReloadListener = (changedPaths?: string[], project?: ReloadProjectInfo) => void;
|
package/src/src/utils/version.ts
CHANGED
|
@@ -3,7 +3,7 @@ import { getEnv } from "../platform/compat/process.js";
|
|
|
3
3
|
|
|
4
4
|
// Keep in sync with deno.json version.
|
|
5
5
|
// scripts/release.ts updates this constant during releases.
|
|
6
|
-
export const VERSION = "0.1.
|
|
6
|
+
export const VERSION = "0.1.94";
|
|
7
7
|
|
|
8
8
|
export function normalizeVeryfrontVersion(version: string | undefined): string | undefined {
|
|
9
9
|
if (!version) return undefined;
|