Skip to content
Merged
Show file tree
Hide file tree
Changes from 8 commits
Commits
Show all changes
32 commits
Select commit Hold shift + click to select a range
fc2ed31
Keep project env fetch credentials authoritative
kojiwakayama Aug 3, 2026
c5da86b
Keep same-origin SSR module imports on pinned paths
kojiwakayama Aug 3, 2026
aecfdcf
Keep prebundled RSC scripts aligned with pinned module paths
kojiwakayama Aug 3, 2026
20283a9
Separate SSR module pinning transports
kojiwakayama Aug 3, 2026
fa31d3b
fix(ssr): canonicalize HTTP module specifiers
kojiwakayama Aug 3, 2026
a1227db
fix(mdx): preserve trusted local project identity
kojiwakayama Aug 3, 2026
53e7d2b
fix(ssr): preserve nested protocol-relative URLs
kojiwakayama Aug 3, 2026
64237b2
fix(mdx): validate pinned module path transport
kojiwakayama Aug 3, 2026
5872f71
test(ssr): name local-project loader argument
kojiwakayama Aug 3, 2026
33f6e70
test(modules): remove vacuous fetch assertions
kojiwakayama Aug 3, 2026
26350e5
fix(ssr): harden protocol-relative resolution and address review find…
kwakayama Aug 3, 2026
190f488
test(mdx): migrate remaining loadModuleESM call site to options object
kwakayama Aug 3, 2026
d488ddb
Fail closed on malformed pinned module paths
kojiwakayama Aug 3, 2026
32035c2
Preserve MDX loader compatibility while rejecting bad pins
kojiwakayama Aug 3, 2026
0ac8d33
Classify reserved dependency pin paths
kojiwakayama Aug 3, 2026
3296f83
Handle undefined MDX load options
kojiwakayama Aug 3, 2026
02d0801
Keep MDX review coverage colocated
kojiwakayama Aug 3, 2026
cff5921
Close remaining MDX review gaps
kojiwakayama Aug 3, 2026
8d21877
Reject nested dependency pin path markers
kojiwakayama Aug 3, 2026
244a57b
Remove obsolete pin encoding helper
kojiwakayama Aug 3, 2026
b09a711
Preserve nested project pin paths and refresh RSC bundle
kojiwakayama Aug 3, 2026
5594eee
Preserve ordinary _pins source paths under snapshot wrappers
kojiwakayama Aug 3, 2026
ae08709
Reject nested dependency pin path markers
kojiwakayama Aug 3, 2026
80351a6
Keep CI checks deterministic for PR 3332
kojiwakayama Aug 3, 2026
6d3f8b3
Merge main into PR 3332 after request-init helper changes
kojiwakayama Aug 3, 2026
958de92
Fail closed on malformed pinned MDX transport errors
kojiwakayama Aug 3, 2026
c2562b3
Integrate pinned SSR follow-up with current main
kojiwakayama Aug 3, 2026
ed89012
Preserve poisoned-prefix resolver safety
kojiwakayama Aug 3, 2026
1110818
Honor guarded fetch test boundaries
kojiwakayama Aug 3, 2026
7e451d6
Keep pinned SSR transport current with main
kojiwakayama Aug 3, 2026
e1a444c
Stabilize import-map capacity retry coverage
kojiwakayama Aug 3, 2026
ae24ffc
Merge origin/main into codex/pr3290-post-merge-fixes
kojiwakayama Aug 3, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 5 additions & 6 deletions src/modules/server/module-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2486,12 +2486,11 @@ describe({ name: "serveModule", sanitizeResources: false, sanitizeOps: false },
ssrParentUrl.searchParams.set("ssr", "true");
const ssrParentResponse = await serve(new Request(ssrParentUrl), projectDir);
assertEquals(ssrParentResponse.status, 200);
for (const childName of ["Absolute.js", "Protocol.js"]) {
const childFetch = nestedFetches.find((href) =>
new URL(href).pathname.endsWith(`/shared/${childName}`)
);
assertEquals(childFetch !== undefined, true);
const childUrl = new URL(childFetch!);
const ssrParentCode = await ssrParentResponse.text();
assertStringIncludes(ssrParentCode, absolutePath);
assertStringIncludes(ssrParentCode, protocolPath);
for (const childFetch of nestedFetches) {
const childUrl = new URL(childFetch);
assertEquals(childUrl.searchParams.get("ssr"), "true");
assertEquals(childUrl.searchParams.get("pins"), snapshot.cacheKey);
}
Expand Down
1 change: 1 addition & 0 deletions src/rendering/factories/service-factories.ts
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,7 @@ export function createPageRenderer(
componentRegistry,
compileMDX,
moduleServerUrl: ctx.moduleServerUrl,
isLocalProject: ctx.isLocalProject === true,
});
}

Expand Down
5 changes: 5 additions & 0 deletions src/rendering/layouts/layout-applicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,8 @@ export interface LayoutApplicationOptions {
dependencyPinningCacheKey?: string;
dependencyPinningDependencies?: Readonly<Record<string, string>>;
dependencyPinningSource?: DependencyPinningSourceInput;
/** Server-trusted local-project identity. */
isLocalProject?: boolean;
}

export class LayoutApplicator {
Expand All @@ -78,6 +80,7 @@ export class LayoutApplicator {
private readonly dependencyPinningCacheKey?: string;
private readonly dependencyPinningDependencies?: Readonly<Record<string, string>>;
private readonly dependencyPinningSource?: DependencyPinningSourceInput;
private readonly isLocalProject: boolean;
private reactVersionPromise: Promise<string> | null = null;
private frameworkProviderModulesPromise?: Promise<{
PageContextProvider: BundledReact.ComponentType<Record<string, unknown>>;
Expand All @@ -104,6 +107,7 @@ export class LayoutApplicator {
this.dependencyPinningCacheKey = options.dependencyPinningCacheKey;
this.dependencyPinningDependencies = options.dependencyPinningDependencies;
this.dependencyPinningSource = options.dependencyPinningSource;
this.isLocalProject = options.isLocalProject === true;
}

private getReactVersion(): Promise<string> {
Expand Down Expand Up @@ -288,6 +292,7 @@ export class LayoutApplicator {
this.dependencyPinningDependencies,
this.dependencyPinningSource,
this.requestUrl?.origin,
this.isLocalProject,
);
}

Expand Down
3 changes: 3 additions & 0 deletions src/rendering/layouts/utils/applicator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ export function applyLayoutsESM(
dependencyPinningDependencies?: Readonly<Record<string, string>>,
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
isLocalProject?: boolean,
): Promise<BundledReact.ReactElement> {
return withSpan(
SpanNames.LAYOUT_APPLY_LAYOUTS_ESM,
Expand Down Expand Up @@ -83,6 +84,7 @@ export function applyLayoutsESM(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
isLocalProject,
),
spanAttrs,
);
Expand Down Expand Up @@ -145,6 +147,7 @@ export function applyLayoutsESM(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
isLocalProject,
),
{ "layout.kind": "mdx", "layout.type": "named" },
);
Expand Down
6 changes: 6 additions & 0 deletions src/rendering/layouts/utils/component-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -346,6 +346,7 @@ export function loadMDXLayout(
dependencyPinningDependencies?: Readonly<Record<string, string>>,
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
isLocalProject?: boolean,
): Promise<BundledReact.ComponentType<{ components?: MDXComponents }> | undefined> {
return withSpan(
SpanNames.LAYOUT_LOAD_MDX,
Expand Down Expand Up @@ -378,6 +379,7 @@ export function loadMDXLayout(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
isLocalProject,
)) as MDXModule;

loadMdxLayoutLog.debug("loadModuleESM DONE", {
Expand Down Expand Up @@ -408,6 +410,7 @@ export async function preloadMDXLayoutModule(
dependencyPinningDependencies?: Readonly<Record<string, string>>,
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
isLocalProject?: boolean,
): Promise<void> {
await loadMDXLayout(
bundle,
Expand All @@ -422,6 +425,7 @@ export async function preloadMDXLayoutModule(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
isLocalProject,
);
}

Expand Down Expand Up @@ -508,6 +512,7 @@ export async function applyMDXLayout(
dependencyPinningDependencies?: Readonly<Record<string, string>>,
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
isLocalProject?: boolean,
): Promise<BundledReact.ReactElement> {
const React = await getProjectReact(reactVersion);
const LayoutFn = await loadMDXLayout(
Expand All @@ -523,6 +528,7 @@ export async function applyMDXLayout(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
isLocalProject,
);

if (!LayoutFn) {
Expand Down
50 changes: 50 additions & 0 deletions src/rendering/orchestrator/layout.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
import "#veryfront/schemas/_test-setup.ts";
import { assertEquals } from "#veryfront/testing/assert.ts";
import { describe, it } from "#veryfront/testing/bdd.ts";
import { createMockAdapter } from "#veryfront/platform/adapters/mock.ts";
import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts";
import type { LayoutItem } from "#veryfront/types";
import { LayoutOrchestrator } from "./layout.ts";

describe("rendering/orchestrator/layout", () => {
it("threads the trusted local-project identity through MDX layout preloading", async () => {
const originalLoadModuleESM = mdxRenderer.loadModuleESM;
const mutableRenderer = mdxRenderer as unknown as {
loadModuleESM: typeof mdxRenderer.loadModuleESM;
};
let observedIsLocalProject: unknown;
mutableRenderer.loadModuleESM = ((...args: unknown[]) => {
observedIsLocalProject = args[11];
return Promise.resolve({ default: () => null });
}) as typeof mdxRenderer.loadModuleESM;

const orchestrator = new LayoutOrchestrator({
projectDir: "/project",
projectId: "local-project",
Comment thread
coderabbitai[bot] marked this conversation as resolved.
Outdated
projectSlug: "local-project",
contentSourceId: "local-main",
adapter: createMockAdapter(),
config: { react: { version: "19.1.1" } },
mode: "development",
layoutCollector: {} as never,
layoutCompiler: {} as never,
layoutCache: {} as never,
componentRegistry: {},
isLocalProject: true,
});
const layouts = [{
kind: "mdx",
path: "/project/layout.mdx",
bundle: { compiledCode: "export default function Layout() { return null; }" },
}] as LayoutItem[];

try {
const result = await orchestrator.preloadLayoutModules(layouts);

assertEquals(result.mdxSuccess, 1);
assertEquals(observedIsLocalProject, true);
} finally {
mutableRenderer.loadModuleESM = originalLoadModuleESM;
}
});
});
4 changes: 4 additions & 0 deletions src/rendering/orchestrator/layout.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,8 @@ export interface LayoutOrchestratorConfig {
layoutCompiler: LayoutCompiler;
layoutCache: LayoutComponentCache;
componentRegistry: MDXComponents | ComponentRegistry;
/** Server-trusted local-project identity. */
isLocalProject?: boolean;
}

function isSnapshotAwareComponentRegistry(
Expand Down Expand Up @@ -249,6 +251,7 @@ export class LayoutOrchestrator {
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
this.config.isLocalProject === true,
);
return { type: "mdx" as const, path: layout.path, success: true };
} catch (error) {
Expand Down Expand Up @@ -376,6 +379,7 @@ export class LayoutOrchestrator {
dependencyPinningCacheKey,
dependencyPinningDependencies,
dependencyPinningSource,
isLocalProject: this.config.isLocalProject === true,
});

const pageType = pageElement.type;
Expand Down
5 changes: 5 additions & 0 deletions src/rendering/orchestrator/lifecycle.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ export interface LifecycleOptions {
projectId?: string;
/** Content source identifier for cache isolation (branch or release) */
contentSourceId?: string;
/** Server-trusted local-project identity. */
isLocalProject?: boolean;
/** Injectable factory for testing — bypasses real service construction */
servicesFactory?: (adapter: RuntimeAdapter) => RendererServices;
}
Expand All @@ -62,6 +64,7 @@ export class RendererLifecycle {
private moduleServerUrl?: string;
private projectId?: string;
private contentSourceId?: string;
private isLocalProject: boolean;
private services?: RendererServices;
private adapter!: RuntimeAdapter;
private servicesFactory?: (adapter: RuntimeAdapter) => RendererServices;
Expand All @@ -72,6 +75,7 @@ export class RendererLifecycle {
this.moduleServerUrl = options.moduleServerUrl;
this.projectId = options.projectId;
this.contentSourceId = options.contentSourceId;
this.isLocalProject = options.isLocalProject === true;
this.servicesFactory = options.servicesFactory;
}

Expand Down Expand Up @@ -181,6 +185,7 @@ export class RendererLifecycle {
componentRegistry,
compileMDX: compileMDXProxy,
moduleServerUrl: this.moduleServerUrl,
isLocalProject: this.isLocalProject,
});

const pageResolver = new PageResolver({
Expand Down
2 changes: 2 additions & 0 deletions src/rendering/orchestrator/ssr.ts
Original file line number Diff line number Diff line change
Expand Up @@ -90,6 +90,7 @@ export class VeryfrontRenderer {
moduleServerUrl: this.moduleServerUrl,
projectId: this.projectId,
contentSourceId: this.contentSourceId,
isLocalProject: this.isLocalProject,
});
this.services = await this.lifecycle.initialize();

Expand Down Expand Up @@ -127,6 +128,7 @@ export class VeryfrontRenderer {
layoutCompiler: this.services.layoutCompiler,
layoutCache: createLayoutComponentCache(),
componentRegistry: this.services.componentRegistry,
isLocalProject: this.isLocalProject,
});

this.htmlGenerator = new HTMLGenerator({
Expand Down
5 changes: 5 additions & 0 deletions src/rendering/page-renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -58,6 +58,7 @@ export class PageRenderer {
filePath?: string,
) => Promise<PageBundle>;
private readonly moduleServerUrl?: string;
private readonly isLocalProject: boolean;
private reactVersionPromise: Promise<string> | null = null;

constructor(options: {
Expand All @@ -72,6 +73,8 @@ export class PageRenderer {
filePath?: string,
) => Promise<PageBundle>;
moduleServerUrl?: string;
/** Server-trusted local-project identity. */
isLocalProject?: boolean;
}) {
this.projectDir = options.projectDir;
this.mode = options.mode;
Expand All @@ -80,6 +83,7 @@ export class PageRenderer {
this.componentRegistry = options.componentRegistry;
this.compileMDX = options.compileMDX;
this.moduleServerUrl = options.moduleServerUrl;
this.isLocalProject = options.isLocalProject === true;
}

private async getMergedComponents(
Expand Down Expand Up @@ -266,6 +270,7 @@ export class PageRenderer {
dependencyPinningCacheKey: options?.dependencyPinningCacheKey,
dependencyPinningDependencies: options?.dependencyPinningDependencies,
dependencyPinningSource: options?.dependencyPinningSource,
isLocalProject: this.isLocalProject,
},
),
{ "render.mdx_path": pageInfo.entity.path },
Expand Down
46 changes: 46 additions & 0 deletions src/rendering/page-rendering.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts";
import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts";
import type { EntityInfo } from "#veryfront/types";
import { handleMDXPage, prepareMDXPageBundles } from "./page-rendering.ts";
import { PageRenderer } from "./page-renderer.ts";
import {
__setServerModuleLoaderForTests,
resetReactCache,
Expand Down Expand Up @@ -188,4 +189,49 @@ describe("rendering/page-rendering", () => {
mutableRenderer.loadModuleESM = originalLoadModuleESM;
}
});

it("threads the trusted local-project identity from PageRenderer into MDX loading", async () => {
__setServerModuleLoaderForTests(() => Promise.resolve({ default: React }));

const originalLoadModuleESM = mdxRenderer.loadModuleESM;
const mutableRenderer = mdxRenderer as unknown as {
loadModuleESM: typeof mdxRenderer.loadModuleESM;
};
let observedIsLocalProject: unknown;
mutableRenderer.loadModuleESM = ((...args: unknown[]) => {
observedIsLocalProject = args[11];
return Promise.resolve({ default: () => null });
}) as typeof mdxRenderer.loadModuleESM;

const renderer = new PageRenderer({
projectDir: "/project",
mode: "development",
config: { react: { version: "19.1.1" } },
adapter: { fs: {} } as unknown as RuntimeAdapter,
componentRegistry: {
prepareDependencySnapshot: () => Promise.resolve("off"),
getAllAsComponents: () => ({}),
} as never,
compileMDX: async () => ({ compiledCode: "", frontmatter: {}, headings: [] }),
isLocalProject: true,
});

try {
await renderer.preparePageBundles(
createMDXPageInfo("# Local MDX probe"),
"probe",
undefined,
{
projectId: "local-project",
projectSlug: "local-project",
contentSourceId: "local-main",
url: new URL("http://localhost/probe"),
},
);

assertEquals(observedIsLocalProject, true);
} finally {
mutableRenderer.loadModuleESM = originalLoadModuleESM;
}
});
});
3 changes: 3 additions & 0 deletions src/rendering/page-rendering.ts
Original file line number Diff line number Diff line change
Expand Up @@ -156,6 +156,8 @@ export function handleMDXPage(
dependencyPinningDependencies?: Readonly<Record<string, string>>;
/** Exact package source namespace paired with the immutable snapshot. */
dependencyPinningSource?: DependencyPinningSourceInput;
/** Server-trusted local-project identity for dev-only module-server fallback. */
isLocalProject?: boolean;
},
): Promise<MDXPageResult> {
return withSpan(
Expand All @@ -182,6 +184,7 @@ export function handleMDXPage(
options?.dependencyPinningDependencies,
options?.dependencyPinningSource,
options?.url?.origin,
options?.isLocalProject,
)) as MDXModule;

const MDXComp = mod.MDXContent || mod.default;
Expand Down
1 change: 1 addition & 0 deletions src/rendering/renderer.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1172,6 +1172,7 @@ export class Renderer {
layoutCompiler,
layoutCache: this.layoutComponentCache,
componentRegistry,
isLocalProject: ctx.isLocalProject === true,
});

const htmlGenerator = new HTMLGenerator({
Expand Down
Loading
Loading