veryfront 0.1.131 → 0.1.136
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/_dnt.polyfills.d.ts +11 -0
- package/esm/_dnt.polyfills.d.ts.map +1 -1
- package/esm/_dnt.polyfills.js +14 -0
- package/esm/cli/commands/build/handler.js +2 -0
- package/esm/cli/commands/deploy/command.d.ts.map +1 -1
- package/esm/cli/commands/deploy/command.js +2 -0
- package/esm/cli/commands/files/command.d.ts.map +1 -1
- package/esm/cli/commands/files/command.js +1 -0
- package/esm/cli/commands/uploads/command.d.ts.map +1 -1
- package/esm/cli/commands/uploads/command.js +1 -0
- package/esm/cli/help/tips.d.ts +3 -0
- package/esm/cli/help/tips.d.ts.map +1 -1
- package/esm/cli/help/tips.js +15 -1
- package/esm/cli/router.d.ts.map +1 -1
- package/esm/cli/router.js +4 -0
- package/esm/cli/shared/animation.d.ts +3 -0
- package/esm/cli/shared/animation.d.ts.map +1 -0
- package/esm/cli/shared/animation.js +23 -0
- package/esm/cli/templates/manifest.d.ts +6 -0
- package/esm/cli/templates/manifest.js +12 -6
- package/esm/cli/ui/progress.d.ts.map +1 -1
- package/esm/cli/ui/progress.js +13 -1
- package/esm/deno.js +1 -1
- package/esm/src/agent/index.d.ts +1 -1
- package/esm/src/agent/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.d.ts.map +1 -1
- package/esm/src/agent/runtime/ai-stream-handler.js +56 -5
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +21 -3
- package/esm/src/agent/runtime/model-tool-converter.d.ts +5 -1
- package/esm/src/agent/runtime/model-tool-converter.d.ts.map +1 -1
- package/esm/src/agent/runtime/model-tool-converter.js +35 -4
- package/esm/src/agent/runtime/tool-helpers.d.ts +2 -1
- package/esm/src/agent/runtime/tool-helpers.d.ts.map +1 -1
- package/esm/src/agent/runtime/tool-helpers.js +6 -3
- package/esm/src/agent/types.d.ts +19 -0
- package/esm/src/agent/types.d.ts.map +1 -1
- package/esm/src/channels/control-plane.d.ts +67 -0
- package/esm/src/channels/control-plane.d.ts.map +1 -1
- package/esm/src/channels/control-plane.js +27 -0
- package/esm/src/discovery/handlers/tool-handler.d.ts.map +1 -1
- package/esm/src/discovery/handlers/tool-handler.js +12 -2
- package/esm/src/html/html-injection.d.ts +2 -0
- package/esm/src/html/html-injection.d.ts.map +1 -1
- package/esm/src/html/html-injection.js +10 -5
- package/esm/src/html/nonce-injection.d.ts +3 -0
- package/esm/src/html/nonce-injection.d.ts.map +1 -0
- package/esm/src/html/nonce-injection.js +249 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts +1 -0
- package/esm/src/internal-agents/ag-ui-sse.d.ts.map +1 -1
- package/esm/src/internal-agents/ag-ui-sse.js +18 -0
- package/esm/src/internal-agents/run-stream.d.ts.map +1 -1
- package/esm/src/internal-agents/run-stream.js +26 -4
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts +1 -0
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.d.ts.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/proxy-manager.js +15 -1
- 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.map +1 -1
- package/esm/src/platform/adapters/fs/veryfront/websocket-manager.js +2 -0
- package/esm/src/proxy/handler.d.ts.map +1 -1
- package/esm/src/proxy/handler.js +25 -5
- package/esm/src/react/components/Head.d.ts +9 -0
- package/esm/src/react/components/Head.d.ts.map +1 -1
- package/esm/src/react/components/Head.js +9 -0
- package/esm/src/react/context/index.d.ts +9 -0
- package/esm/src/react/context/index.d.ts.map +1 -1
- package/esm/src/react/context/index.js +9 -0
- package/esm/src/react/router/index.d.ts +9 -0
- package/esm/src/react/router/index.d.ts.map +1 -1
- package/esm/src/react/router/index.js +9 -0
- package/esm/src/rendering/orchestrator/html.d.ts +1 -0
- package/esm/src/rendering/orchestrator/html.d.ts.map +1 -1
- package/esm/src/rendering/orchestrator/html.js +81 -89
- package/esm/src/rendering/script-page-handling.js +1 -0
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +9 -0
- package/esm/src/server/handlers/request/ssr/ssr-response-builder.d.ts.map +1 -1
- package/esm/src/server/handlers/request/ssr/ssr-response-builder.js +3 -7
- package/esm/src/server/handlers/request/static.handler.d.ts.map +1 -1
- package/esm/src/server/handlers/request/static.handler.js +18 -10
- package/esm/src/tool/factory.d.ts.map +1 -1
- package/esm/src/tool/factory.js +14 -4
- package/esm/src/tool/types.d.ts +2 -0
- package/esm/src/tool/types.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.d.ts +1 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/constants.js +3 -0
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.js +4 -2
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/index.js +10 -9
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.d.ts.map +1 -1
- package/esm/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.js +3 -1
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/package.json +1 -1
- package/src/_dnt.polyfills.ts +27 -0
- package/src/cli/commands/build/handler.ts +3 -0
- package/src/cli/commands/deploy/command.ts +3 -0
- package/src/cli/commands/files/command.ts +1 -0
- package/src/cli/commands/uploads/command.ts +3 -0
- package/src/cli/help/tips.ts +18 -1
- package/src/cli/router.ts +5 -0
- package/src/cli/shared/animation.ts +25 -0
- package/src/cli/templates/manifest.js +12 -6
- package/src/cli/ui/progress.ts +13 -1
- package/src/deno.js +1 -1
- package/src/src/agent/index.ts +2 -0
- package/src/src/agent/runtime/ai-stream-handler.ts +64 -6
- package/src/src/agent/runtime/index.ts +26 -1
- package/src/src/agent/runtime/model-tool-converter.ts +47 -3
- package/src/src/agent/runtime/tool-helpers.ts +15 -3
- package/src/src/agent/types.ts +23 -0
- package/src/src/channels/control-plane.ts +31 -0
- package/src/src/discovery/handlers/tool-handler.ts +13 -2
- package/src/src/html/html-injection.ts +16 -5
- package/src/src/html/nonce-injection.ts +300 -0
- package/src/src/internal-agents/ag-ui-sse.ts +20 -0
- package/src/src/internal-agents/run-stream.ts +35 -4
- package/src/src/platform/adapters/fs/veryfront/proxy-manager.ts +29 -3
- package/src/src/platform/adapters/fs/veryfront/types.ts +2 -0
- package/src/src/platform/adapters/fs/veryfront/websocket-manager.ts +2 -0
- package/src/src/proxy/handler.ts +43 -5
- package/src/src/react/components/Head.tsx +10 -0
- package/src/src/react/context/index.tsx +10 -0
- package/src/src/react/router/index.tsx +10 -0
- package/src/src/rendering/orchestrator/html.ts +125 -100
- package/src/src/rendering/script-page-handling.ts +1 -0
- package/src/src/server/handlers/dev/framework-candidates.generated.ts +9 -0
- package/src/src/server/handlers/request/ssr/ssr-response-builder.ts +7 -11
- package/src/src/server/handlers/request/static.handler.ts +22 -10
- package/src/src/tool/factory.ts +17 -4
- package/src/src/tool/types.ts +2 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/constants.ts +4 -0
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/import-finder.ts +4 -2
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/index.ts +18 -15
- package/src/src/transforms/pipeline/stages/ssr-vf-modules/path-resolver.ts +4 -1
- package/src/src/utils/version-constant.ts +1 -1
|
@@ -20,6 +20,7 @@ export interface RunFinishedMetadata {
|
|
|
20
20
|
export interface StreamTransformState {
|
|
21
21
|
messageId: string | null;
|
|
22
22
|
textOpen: boolean;
|
|
23
|
+
sawVisibleOutput: boolean;
|
|
23
24
|
sawTerminalError: boolean;
|
|
24
25
|
metadata: RunFinishedMetadata;
|
|
25
26
|
}
|
|
@@ -28,6 +29,7 @@ export function createStreamTransformState(): StreamTransformState {
|
|
|
28
29
|
return {
|
|
29
30
|
messageId: null,
|
|
30
31
|
textOpen: false,
|
|
32
|
+
sawVisibleOutput: false,
|
|
31
33
|
sawTerminalError: false,
|
|
32
34
|
metadata: {},
|
|
33
35
|
};
|
|
@@ -181,6 +183,7 @@ export function mapRuntimeEventToAgUi(
|
|
|
181
183
|
if (state.textOpen) return [];
|
|
182
184
|
const messageId = getMessageId(state, event);
|
|
183
185
|
state.textOpen = true;
|
|
186
|
+
state.sawVisibleOutput = true;
|
|
184
187
|
return [{
|
|
185
188
|
event: "TextMessageStart",
|
|
186
189
|
payload: { messageId, role: "assistant" },
|
|
@@ -189,6 +192,7 @@ export function mapRuntimeEventToAgUi(
|
|
|
189
192
|
|
|
190
193
|
case "text-delta": {
|
|
191
194
|
const messageId = getMessageId(state, event);
|
|
195
|
+
state.sawVisibleOutput = true;
|
|
192
196
|
if (!state.textOpen) {
|
|
193
197
|
state.textOpen = true;
|
|
194
198
|
return [
|
|
@@ -216,6 +220,7 @@ export function mapRuntimeEventToAgUi(
|
|
|
216
220
|
}
|
|
217
221
|
|
|
218
222
|
case "tool-input-start":
|
|
223
|
+
state.sawVisibleOutput = true;
|
|
219
224
|
return [{
|
|
220
225
|
event: "ToolCallStart",
|
|
221
226
|
payload: {
|
|
@@ -225,6 +230,7 @@ export function mapRuntimeEventToAgUi(
|
|
|
225
230
|
}];
|
|
226
231
|
|
|
227
232
|
case "tool-input-delta":
|
|
233
|
+
state.sawVisibleOutput = true;
|
|
228
234
|
return [{
|
|
229
235
|
event: "ToolCallArgs",
|
|
230
236
|
payload: {
|
|
@@ -234,12 +240,14 @@ export function mapRuntimeEventToAgUi(
|
|
|
234
240
|
}];
|
|
235
241
|
|
|
236
242
|
case "tool-input-available":
|
|
243
|
+
state.sawVisibleOutput = true;
|
|
237
244
|
return [{
|
|
238
245
|
event: "ToolCallEnd",
|
|
239
246
|
payload: { toolCallId: event.toolCallId },
|
|
240
247
|
}];
|
|
241
248
|
|
|
242
249
|
case "tool-output-available":
|
|
250
|
+
state.sawVisibleOutput = true;
|
|
243
251
|
return [{
|
|
244
252
|
event: "ToolCallResult",
|
|
245
253
|
payload: {
|
|
@@ -249,6 +257,7 @@ export function mapRuntimeEventToAgUi(
|
|
|
249
257
|
}];
|
|
250
258
|
|
|
251
259
|
case "tool-output-error":
|
|
260
|
+
state.sawVisibleOutput = true;
|
|
252
261
|
return [{
|
|
253
262
|
event: "ToolCallResult",
|
|
254
263
|
payload: {
|
|
@@ -292,6 +301,17 @@ export function finalizeRunEvents(
|
|
|
292
301
|
return [];
|
|
293
302
|
}
|
|
294
303
|
|
|
304
|
+
if (!state.sawVisibleOutput) {
|
|
305
|
+
state.sawTerminalError = true;
|
|
306
|
+
return [{
|
|
307
|
+
event: "RunError",
|
|
308
|
+
payload: {
|
|
309
|
+
code: "EMPTY_ASSISTANT_OUTPUT",
|
|
310
|
+
message: "Agent run produced no assistant-visible output",
|
|
311
|
+
},
|
|
312
|
+
}];
|
|
313
|
+
}
|
|
314
|
+
|
|
295
315
|
const events: Array<{ event: string; payload: Record<string, unknown> }> = [];
|
|
296
316
|
if (state.textOpen) {
|
|
297
317
|
state.textOpen = false;
|
|
@@ -20,6 +20,12 @@ import type { RuntimeRunAgentInput } from "./schema.js";
|
|
|
20
20
|
|
|
21
21
|
const anyObjectSchema = z.record(z.string(), z.unknown());
|
|
22
22
|
|
|
23
|
+
type RuntimeFilteredAgent = Agent & {
|
|
24
|
+
config: Agent["config"] & {
|
|
25
|
+
__vfAllowedRemoteTools?: string[];
|
|
26
|
+
};
|
|
27
|
+
};
|
|
28
|
+
|
|
23
29
|
export interface RuntimeAgentStreamExecutionDeps {
|
|
24
30
|
sessionManager: AgentRunSessionManager;
|
|
25
31
|
createRuntime?: (
|
|
@@ -203,6 +209,22 @@ function normalizeRuntimeMessages(messages: RuntimeRunAgentInput["messages"]): M
|
|
|
203
209
|
}));
|
|
204
210
|
}
|
|
205
211
|
|
|
212
|
+
function getAllowedRemoteToolNames(
|
|
213
|
+
forwardedProps: RuntimeRunAgentInput["forwardedProps"],
|
|
214
|
+
): string[] | undefined {
|
|
215
|
+
const runtimeOverrides = isRecord(forwardedProps?.runtimeOverrides)
|
|
216
|
+
? forwardedProps.runtimeOverrides
|
|
217
|
+
: null;
|
|
218
|
+
if (!runtimeOverrides || !Object.hasOwn(runtimeOverrides, "allowedTools")) {
|
|
219
|
+
return undefined;
|
|
220
|
+
}
|
|
221
|
+
const allowedTools = runtimeOverrides.allowedTools;
|
|
222
|
+
if (!Array.isArray(allowedTools)) {
|
|
223
|
+
return [];
|
|
224
|
+
}
|
|
225
|
+
return allowedTools.every((toolName) => typeof toolName === "string") ? allowedTools : [];
|
|
226
|
+
}
|
|
227
|
+
|
|
206
228
|
export async function createRuntimeAgentStreamResponse(
|
|
207
229
|
input: RuntimeRunAgentInput,
|
|
208
230
|
agent: Agent,
|
|
@@ -214,10 +236,19 @@ export async function createRuntimeAgentStreamResponse(
|
|
|
214
236
|
});
|
|
215
237
|
|
|
216
238
|
const mergedTools = buildMergedTools(agent, input, deps.sessionManager);
|
|
217
|
-
const
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
239
|
+
const allowedRemoteToolNames = getAllowedRemoteToolNames(input.forwardedProps);
|
|
240
|
+
const runtimeAgent: RuntimeFilteredAgent = {
|
|
241
|
+
...agent,
|
|
242
|
+
config: {
|
|
243
|
+
...agent.config,
|
|
244
|
+
tools: mergedTools,
|
|
245
|
+
...(allowedRemoteToolNames !== undefined
|
|
246
|
+
? { __vfAllowedRemoteTools: allowedRemoteToolNames }
|
|
247
|
+
: {}),
|
|
248
|
+
},
|
|
249
|
+
};
|
|
250
|
+
const runtime = deps.createRuntime?.(runtimeAgent, mergedTools) ??
|
|
251
|
+
new AgentRuntime(runtimeAgent.id, runtimeAgent.config);
|
|
221
252
|
|
|
222
253
|
let completedResponse: AgentResponse | null = null;
|
|
223
254
|
const runtimeMessages = normalizeRuntimeMessages(input.messages);
|
|
@@ -398,9 +398,11 @@ export class ProxyFSAdapterManager {
|
|
|
398
398
|
projectId,
|
|
399
399
|
apiToken: effectiveToken,
|
|
400
400
|
},
|
|
401
|
-
invalidationCallbacks: createDefaultInvalidationCallbacks(
|
|
402
|
-
this.baseConfig.invalidationCallbacks,
|
|
403
|
-
|
|
401
|
+
invalidationCallbacks: createDefaultInvalidationCallbacks({
|
|
402
|
+
...this.baseConfig.invalidationCallbacks,
|
|
403
|
+
evictCurrentAdapter: () =>
|
|
404
|
+
this.evictAdapter(projectSlug, productionMode, releaseId, branch),
|
|
405
|
+
}),
|
|
404
406
|
};
|
|
405
407
|
|
|
406
408
|
const adapter = new VeryfrontFSAdapter(config);
|
|
@@ -557,6 +559,30 @@ export class ProxyFSAdapterManager {
|
|
|
557
559
|
return this.adapters.has(cacheKey);
|
|
558
560
|
}
|
|
559
561
|
|
|
562
|
+
evictAdapter(
|
|
563
|
+
projectSlug: string,
|
|
564
|
+
productionMode?: boolean,
|
|
565
|
+
releaseId?: string | null,
|
|
566
|
+
branch?: string | null,
|
|
567
|
+
): void {
|
|
568
|
+
const cacheKey = buildProxyManagerCacheKey(
|
|
569
|
+
projectSlug,
|
|
570
|
+
productionMode ?? false,
|
|
571
|
+
releaseId ?? null,
|
|
572
|
+
branch ?? null,
|
|
573
|
+
);
|
|
574
|
+
|
|
575
|
+
const adapter = this.adapters.get(cacheKey);
|
|
576
|
+
if (!adapter) {
|
|
577
|
+
logger.debug("No adapter to evict", { cacheKey });
|
|
578
|
+
return;
|
|
579
|
+
}
|
|
580
|
+
|
|
581
|
+
logger.debug("Evicting adapter", { cacheKey });
|
|
582
|
+
adapter.adapter.dispose();
|
|
583
|
+
this.adapters.delete(cacheKey);
|
|
584
|
+
}
|
|
585
|
+
|
|
560
586
|
getStats(): { adapters: number; stats: Record<string, CacheStats> } {
|
|
561
587
|
const stats: Record<string, CacheStats> = {};
|
|
562
588
|
|
|
@@ -137,4 +137,6 @@ export interface InvalidationCallbacks {
|
|
|
137
137
|
clearProjectCSSCache?: (projectSlug: string) => void;
|
|
138
138
|
/** Clear domain lookup cache to refresh release IDs after publishing */
|
|
139
139
|
clearDomainCache?: () => void;
|
|
140
|
+
/** Evict the current shared proxy adapter after successful invalidation */
|
|
141
|
+
evictCurrentAdapter?: () => void;
|
|
140
142
|
}
|
|
@@ -693,6 +693,7 @@ export class WebSocketManager {
|
|
|
693
693
|
previewInvalidationPrefixes,
|
|
694
694
|
previewInvalidationVersion,
|
|
695
695
|
);
|
|
696
|
+
this.deps.invalidationCallbacks.evictCurrentAdapter?.();
|
|
696
697
|
}
|
|
697
698
|
}
|
|
698
699
|
}
|
|
@@ -837,6 +838,7 @@ export class WebSocketManager {
|
|
|
837
838
|
previewInvalidationPrefixes,
|
|
838
839
|
previewInvalidationVersion,
|
|
839
840
|
);
|
|
841
|
+
this.deps.invalidationCallbacks.evictCurrentAdapter?.();
|
|
840
842
|
}
|
|
841
843
|
}
|
|
842
844
|
}
|
package/src/src/proxy/handler.ts
CHANGED
|
@@ -410,6 +410,24 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
|
|
|
410
410
|
);
|
|
411
411
|
}
|
|
412
412
|
|
|
413
|
+
function makeProjectNotFoundContext(
|
|
414
|
+
base: {
|
|
415
|
+
scope: TokenScope;
|
|
416
|
+
host: string;
|
|
417
|
+
parsedDomain: ParsedDomain;
|
|
418
|
+
},
|
|
419
|
+
token?: string,
|
|
420
|
+
): ProxyContext {
|
|
421
|
+
return makeErrorContext(
|
|
422
|
+
base,
|
|
423
|
+
404,
|
|
424
|
+
"Project not found",
|
|
425
|
+
token,
|
|
426
|
+
undefined,
|
|
427
|
+
"project-not-found",
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
|
|
413
431
|
async function resolveRequestToken(
|
|
414
432
|
req: dntShim.Request,
|
|
415
433
|
scope: TokenScope,
|
|
@@ -607,20 +625,30 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
|
|
|
607
625
|
},
|
|
608
626
|
));
|
|
609
627
|
|
|
610
|
-
if (projectSlug &&
|
|
628
|
+
if (projectSlug && !token) {
|
|
611
629
|
const status = parseStatusFromError(tokenFetchError);
|
|
612
630
|
if (status === 404) {
|
|
613
|
-
|
|
614
|
-
|
|
631
|
+
if (scope === "preview") {
|
|
632
|
+
logger?.info("Preview project not found", { projectSlug, host });
|
|
633
|
+
return makePreviewProjectNotFoundContext(base);
|
|
634
|
+
}
|
|
635
|
+
|
|
636
|
+
logger?.info("Project not found", { projectSlug, host, scope });
|
|
637
|
+
return makeProjectNotFoundContext(base);
|
|
615
638
|
}
|
|
616
639
|
|
|
617
|
-
|
|
640
|
+
const message = scope === "preview"
|
|
641
|
+
? "Failed to authenticate preview request"
|
|
642
|
+
: "Failed to authenticate project request";
|
|
643
|
+
|
|
644
|
+
logger?.warn("Project request has no usable token", {
|
|
618
645
|
projectSlug,
|
|
619
646
|
host,
|
|
647
|
+
scope,
|
|
620
648
|
hadUserToken: !!userToken,
|
|
621
649
|
hadTokenFetchError: !!tokenFetchError,
|
|
622
650
|
});
|
|
623
|
-
return makeErrorContext(base, 502,
|
|
651
|
+
return makeErrorContext(base, 502, message);
|
|
624
652
|
}
|
|
625
653
|
|
|
626
654
|
if (isCustomDomain && !projectSlug) {
|
|
@@ -692,6 +720,16 @@ export function createProxyHandler(options: ProxyHandlerOptions) {
|
|
|
692
720
|
);
|
|
693
721
|
}
|
|
694
722
|
|
|
723
|
+
if (!resolved.projectId) {
|
|
724
|
+
logger?.info("Project not found after lookup", {
|
|
725
|
+
projectSlug,
|
|
726
|
+
host,
|
|
727
|
+
scope,
|
|
728
|
+
targetEnvName: parsedDomain.environment,
|
|
729
|
+
});
|
|
730
|
+
return makeProjectNotFoundContext(base, token);
|
|
731
|
+
}
|
|
732
|
+
|
|
695
733
|
projectId = resolved.projectId;
|
|
696
734
|
releaseId = resolved.releaseId;
|
|
697
735
|
environmentId = resolved.environmentId;
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React page-context exports for MDX and route-aware rendering.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { PageContextProvider, usePageContext } from "veryfront/context";
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
1
10
|
import "../../../_dnt.polyfills.js";
|
|
11
|
+
|
|
2
12
|
export { PageContextProvider, usePageContext } from "../runtime/core.js";
|
|
3
13
|
export type { MdxHeading, PageContextProviderProps, PageContextValue } from "../runtime/core.js";
|
|
@@ -1,3 +1,13 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* React router exports for client navigation and route context.
|
|
3
|
+
*
|
|
4
|
+
* @module
|
|
5
|
+
* @example
|
|
6
|
+
* ```tsx
|
|
7
|
+
* import { Link, RouterProvider, useRouter } from "veryfront/router";
|
|
8
|
+
* ```
|
|
9
|
+
*/
|
|
1
10
|
import "../../../_dnt.polyfills.js";
|
|
11
|
+
|
|
2
12
|
export { Link, Router, RouterProvider, useRouter } from "../runtime/core.js";
|
|
3
13
|
export type { LinkProps, RouterProviderProps, RouterValue } from "../runtime/core.js";
|
|
@@ -19,7 +19,7 @@ import type {
|
|
|
19
19
|
PageBundle,
|
|
20
20
|
} from "../../types/index.js";
|
|
21
21
|
import { DEFAULT_DASHBOARD_PORT, rendererLogger } from "../../utils/index.js";
|
|
22
|
-
import {
|
|
22
|
+
import { addNonceToHtmlTags } from "../../html/nonce-injection.js";
|
|
23
23
|
import type { RenderOptions } from "./types.js";
|
|
24
24
|
import { injectElementSelectors } from "../../studio/element-selector-injector.js";
|
|
25
25
|
import { computeSourceHash } from "../../studio/hash-utils.js";
|
|
@@ -41,106 +41,60 @@ import type { ResolvedContentContext } from "../../platform/adapters/fs/veryfron
|
|
|
41
41
|
const logger = rendererLogger.component("html-generator");
|
|
42
42
|
type ProjectCSSResult = Awaited<ReturnType<typeof getProjectCSS>> | null;
|
|
43
43
|
|
|
44
|
-
function
|
|
45
|
-
|
|
44
|
+
function applyExplicitThemeToDocument(
|
|
45
|
+
html: string,
|
|
46
|
+
colorScheme: "light" | "dark" | undefined,
|
|
47
|
+
enabled: boolean | undefined,
|
|
48
|
+
): string {
|
|
49
|
+
if (!enabled || !colorScheme) return html;
|
|
46
50
|
|
|
47
|
-
|
|
48
|
-
|
|
51
|
+
return html.replace(/<html\b([^>]*)>/i, (_match, attrs: string) => {
|
|
52
|
+
let nextAttrs = attrs;
|
|
49
53
|
|
|
50
|
-
if (
|
|
51
|
-
|
|
52
|
-
continue;
|
|
54
|
+
if (/\sdata-theme\s*=/i.test(nextAttrs)) {
|
|
55
|
+
nextAttrs = nextAttrs.replace(/\sdata-theme\s*=\s*(["']).*?\1/i, "");
|
|
53
56
|
}
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
if (tagName === "script" || tagName === "style") return tagName;
|
|
70
|
-
return undefined;
|
|
71
|
-
}
|
|
72
|
-
|
|
73
|
-
function injectNonceIntoOpeningTag(tag: string, escapedNonce: string): string {
|
|
74
|
-
if (/\bnonce\s*=/iu.test(tag)) return tag;
|
|
75
|
-
|
|
76
|
-
const closeIndex = tag.lastIndexOf(">");
|
|
77
|
-
if (closeIndex === -1) return tag;
|
|
78
|
-
|
|
79
|
-
const insertAt = /\/\s*>$/u.test(tag) ? closeIndex - 1 : closeIndex;
|
|
80
|
-
return `${tag.slice(0, insertAt)} nonce="${escapedNonce}"${tag.slice(insertAt)}`;
|
|
81
|
-
}
|
|
82
|
-
|
|
83
|
-
function addNonceToRenderedTags(html: string, nonce?: string): string {
|
|
84
|
-
if (!nonce) return html;
|
|
85
|
-
|
|
86
|
-
const escapedNonce = escapeHtml(nonce);
|
|
87
|
-
const lowerHtml = html.toLowerCase();
|
|
88
|
-
let result = "";
|
|
89
|
-
let index = 0;
|
|
90
|
-
let rawTextTag: "script" | "style" | null = null;
|
|
91
|
-
|
|
92
|
-
while (index < html.length) {
|
|
93
|
-
if (rawTextTag) {
|
|
94
|
-
const closingIndex = lowerHtml.indexOf(`</${rawTextTag}`, index);
|
|
95
|
-
if (closingIndex === -1) {
|
|
96
|
-
result += html.slice(index);
|
|
97
|
-
break;
|
|
57
|
+
nextAttrs += ` data-theme="${colorScheme}"`;
|
|
58
|
+
|
|
59
|
+
const styleMatch = nextAttrs.match(/\sstyle\s*=\s*(["'])(.*?)\1/i);
|
|
60
|
+
if (styleMatch) {
|
|
61
|
+
let styleValue = (styleMatch[2] ?? "").trim();
|
|
62
|
+
|
|
63
|
+
if (/color-scheme\s*:/i.test(styleValue)) {
|
|
64
|
+
styleValue = styleValue.replace(
|
|
65
|
+
/color-scheme\s*:\s*[^;]+/i,
|
|
66
|
+
`color-scheme: ${colorScheme}`,
|
|
67
|
+
);
|
|
68
|
+
} else {
|
|
69
|
+
styleValue = styleValue
|
|
70
|
+
? `${styleValue.replace(/;?\s*$/, ";")} color-scheme: ${colorScheme};`
|
|
71
|
+
: `color-scheme: ${colorScheme};`;
|
|
98
72
|
}
|
|
99
73
|
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
continue;
|
|
74
|
+
nextAttrs = nextAttrs.replace(styleMatch[0], ` style="${styleValue}"`);
|
|
75
|
+
} else {
|
|
76
|
+
nextAttrs += ` style="color-scheme: ${colorScheme};"`;
|
|
104
77
|
}
|
|
105
78
|
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
result += html.slice(index, endIndex);
|
|
110
|
-
index = endIndex;
|
|
111
|
-
continue;
|
|
112
|
-
}
|
|
113
|
-
|
|
114
|
-
if (html[index] !== "<") {
|
|
115
|
-
result += html[index];
|
|
116
|
-
index++;
|
|
117
|
-
continue;
|
|
118
|
-
}
|
|
119
|
-
|
|
120
|
-
const tagEnd = findTagEnd(html, index);
|
|
121
|
-
if (tagEnd === -1) {
|
|
122
|
-
result += html.slice(index);
|
|
123
|
-
break;
|
|
124
|
-
}
|
|
125
|
-
|
|
126
|
-
const tag = html.slice(index, tagEnd + 1);
|
|
127
|
-
const tagName = getOpeningTagName(tag);
|
|
128
|
-
|
|
129
|
-
if (!tagName) {
|
|
130
|
-
result += tag;
|
|
131
|
-
index = tagEnd + 1;
|
|
132
|
-
continue;
|
|
133
|
-
}
|
|
134
|
-
|
|
135
|
-
result += injectNonceIntoOpeningTag(tag, escapedNonce);
|
|
136
|
-
index = tagEnd + 1;
|
|
137
|
-
|
|
138
|
-
if (!/\/\s*>$/u.test(tag)) {
|
|
139
|
-
rawTextTag = tagName;
|
|
140
|
-
}
|
|
141
|
-
}
|
|
79
|
+
return `<html${nextAttrs}>`;
|
|
80
|
+
});
|
|
81
|
+
}
|
|
142
82
|
|
|
143
|
-
|
|
83
|
+
function injectThemePersistenceScript(
|
|
84
|
+
html: string,
|
|
85
|
+
colorScheme: "light" | "dark" | undefined,
|
|
86
|
+
enabled: boolean | undefined,
|
|
87
|
+
nonce?: string,
|
|
88
|
+
): string {
|
|
89
|
+
if (!enabled || !colorScheme || !/<\/head>/i.test(html)) return html;
|
|
90
|
+
if (html.includes(`localStorage.setItem('theme','${colorScheme}')`)) return html;
|
|
91
|
+
|
|
92
|
+
const nonceAttr = nonce ? ` nonce="${nonce}"` : "";
|
|
93
|
+
const script = `<script${nonceAttr}>
|
|
94
|
+
(function(){try{localStorage.setItem('theme','${colorScheme}')}catch(e){/* SILENT: localStorage may be unavailable */}})();
|
|
95
|
+
</script>`;
|
|
96
|
+
|
|
97
|
+
return html.replace(/<\/head>/i, `${script}\n</head>`);
|
|
144
98
|
}
|
|
145
99
|
|
|
146
100
|
export interface HTMLGeneratorConfig {
|
|
@@ -173,16 +127,29 @@ export class HTMLGenerator {
|
|
|
173
127
|
}
|
|
174
128
|
|
|
175
129
|
async generateFullHTML(context: HTMLGenerationContext): Promise<string> {
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
:
|
|
130
|
+
let html: string;
|
|
131
|
+
if (isFullHTMLDocument(context.html)) {
|
|
132
|
+
let projectCSSPromise: Promise<ProjectCSSResult> | undefined;
|
|
133
|
+
if (this.config.mode === "production" && context.options?.environment === "production") {
|
|
134
|
+
const mergedFrontmatter = this.mergeFrontmatter(context);
|
|
135
|
+
const htmlOptions = await profilePhase(
|
|
136
|
+
"html.build_options",
|
|
137
|
+
() => this.buildHTMLOptions(context, mergedFrontmatter),
|
|
138
|
+
);
|
|
139
|
+
projectCSSPromise = this.startProjectCSSPreparation(context, htmlOptions);
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
html = await this.handleFullHTMLDocument(context, projectCSSPromise);
|
|
143
|
+
} else {
|
|
144
|
+
html = await this.wrapHTMLFragment(context);
|
|
145
|
+
}
|
|
179
146
|
const finalHtml = context.options?.studioEmbed ? injectElementSelectors(html) : html;
|
|
180
147
|
|
|
181
148
|
if (context.options?.studioEmbed) {
|
|
182
149
|
logger.debug("Injected element selectors for Studio");
|
|
183
150
|
}
|
|
184
151
|
|
|
185
|
-
return
|
|
152
|
+
return addNonceToHtmlTags(finalHtml, context.options?.nonce);
|
|
186
153
|
}
|
|
187
154
|
|
|
188
155
|
async generateHTMLStream(
|
|
@@ -210,6 +177,27 @@ export class HTMLGenerator {
|
|
|
210
177
|
reactContent = error.partialContent.trim();
|
|
211
178
|
}
|
|
212
179
|
|
|
180
|
+
if (isFullHTMLDocument(reactContent)) {
|
|
181
|
+
const encoder = new TextEncoder();
|
|
182
|
+
const fullHtml = addNonceToHtmlTags(
|
|
183
|
+
await this.handleFullHTMLDocument(
|
|
184
|
+
{
|
|
185
|
+
...fullContext,
|
|
186
|
+
html: reactContent,
|
|
187
|
+
},
|
|
188
|
+
projectCSSPromise,
|
|
189
|
+
),
|
|
190
|
+
context.options?.nonce,
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
return new ReadableStream({
|
|
194
|
+
start(controller) {
|
|
195
|
+
controller.enqueue(encoder.encode(fullHtml));
|
|
196
|
+
controller.close();
|
|
197
|
+
},
|
|
198
|
+
});
|
|
199
|
+
}
|
|
200
|
+
|
|
213
201
|
const { start, end } = await profilePhase(
|
|
214
202
|
"html.generate_shell_parts",
|
|
215
203
|
() =>
|
|
@@ -223,7 +211,7 @@ export class HTMLGenerator {
|
|
|
223
211
|
);
|
|
224
212
|
|
|
225
213
|
const encoder = new TextEncoder();
|
|
226
|
-
const fullHtml =
|
|
214
|
+
const fullHtml = addNonceToHtmlTags(
|
|
227
215
|
`${start}${reactContent}${end}`,
|
|
228
216
|
context.options?.nonce,
|
|
229
217
|
);
|
|
@@ -236,7 +224,10 @@ export class HTMLGenerator {
|
|
|
236
224
|
});
|
|
237
225
|
}
|
|
238
226
|
|
|
239
|
-
private async handleFullHTMLDocument(
|
|
227
|
+
private async handleFullHTMLDocument(
|
|
228
|
+
context: HTMLGenerationContext,
|
|
229
|
+
projectCSSPromise?: Promise<ProjectCSSResult>,
|
|
230
|
+
): Promise<string> {
|
|
240
231
|
const metadata = extractHTMLMetadata(
|
|
241
232
|
(context.pageInfo.entity.frontmatter || {}) as MDXFrontmatter,
|
|
242
233
|
(context.layoutBundle?.frontmatter || {}) as MDXFrontmatter,
|
|
@@ -251,7 +242,23 @@ export class HTMLGenerator {
|
|
|
251
242
|
}),
|
|
252
243
|
]);
|
|
253
244
|
|
|
254
|
-
const
|
|
245
|
+
const themedHtml = injectThemePersistenceScript(
|
|
246
|
+
applyExplicitThemeToDocument(
|
|
247
|
+
context.html,
|
|
248
|
+
context.options?.colorScheme,
|
|
249
|
+
context.options?.colorSchemeFromParam,
|
|
250
|
+
),
|
|
251
|
+
context.options?.colorScheme,
|
|
252
|
+
context.options?.colorSchemeFromParam,
|
|
253
|
+
context.options?.nonce,
|
|
254
|
+
);
|
|
255
|
+
|
|
256
|
+
const projectStylesheetHref = await this.resolveProjectStylesheetHref(
|
|
257
|
+
context,
|
|
258
|
+
projectCSSPromise,
|
|
259
|
+
);
|
|
260
|
+
|
|
261
|
+
const injectedHtml = injectHTMLContent(themedHtml, "", metadata, {
|
|
255
262
|
mode: this.config.mode,
|
|
256
263
|
slug: context.slug,
|
|
257
264
|
devPort: this.config.config?.dev?.port || DEFAULT_DASHBOARD_PORT,
|
|
@@ -262,6 +269,7 @@ export class HTMLGenerator {
|
|
|
262
269
|
isLocalProject: this.config.mode === "development",
|
|
263
270
|
nonce: context.options?.nonce,
|
|
264
271
|
importMapJson,
|
|
272
|
+
projectStylesheetHref,
|
|
265
273
|
});
|
|
266
274
|
|
|
267
275
|
if (injectedHtml.trimStart().toLowerCase().startsWith("<!doctype")) return injectedHtml;
|
|
@@ -269,6 +277,23 @@ export class HTMLGenerator {
|
|
|
269
277
|
return `<!DOCTYPE html>\n${injectedHtml}`;
|
|
270
278
|
}
|
|
271
279
|
|
|
280
|
+
private async resolveProjectStylesheetHref(
|
|
281
|
+
context: HTMLGenerationContext,
|
|
282
|
+
projectCSSPromise?: Promise<ProjectCSSResult>,
|
|
283
|
+
): Promise<string | undefined> {
|
|
284
|
+
if (!projectCSSPromise) return undefined;
|
|
285
|
+
|
|
286
|
+
const projectCSS = await profilePhase("html.project_css", () => projectCSSPromise);
|
|
287
|
+
const cssHash = projectCSS?.hash ?? "";
|
|
288
|
+
if (cssHash) return `/_vf/css/${cssHash}.css`;
|
|
289
|
+
|
|
290
|
+
logger.error("Project CSS hash is empty for full-document HTML", {
|
|
291
|
+
slug: context.slug,
|
|
292
|
+
environment: context.options?.environment,
|
|
293
|
+
});
|
|
294
|
+
return undefined;
|
|
295
|
+
}
|
|
296
|
+
|
|
272
297
|
private async detectUseClientDirective(pagePath: string): Promise<boolean> {
|
|
273
298
|
try {
|
|
274
299
|
const pageContent = await this.config.adapter.fs.readFile(pagePath);
|