Skip to content
Merged
Show file tree
Hide file tree
Changes from all 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
2 changes: 1 addition & 1 deletion src/modules/import-map/preloader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1324,8 +1324,8 @@ describe("modules/import-map/preloader", () => {
});

await waitForLoadCount(loads, 3);
assertEquals((await first).imports?.source, "a");
loads[2]!.resolve({ imports: { source: "c" } });
assertEquals((await first).imports?.source, "a");
assertEquals((await queued).imports?.source, "c");
loads[1]!.resolve({ imports: { source: "b" } });
assertEquals((await unrelated).imports?.source, "b");
Expand Down
21 changes: 6 additions & 15 deletions src/modules/server/module-server.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2459,7 +2459,6 @@ describe({ name: "serveModule", sanitizeResources: false, sanitizeOps: false },
assertEquals(childResponse.status, 200);
}

const nestedFetches: string[] = [];
await withMockFetch(
async (
input: RequestInfo | URL,
Expand All @@ -2471,7 +2470,6 @@ describe({ name: "serveModule", sanitizeResources: false, sanitizeOps: false },
requestUrl.origin === parentUrl.origin &&
requestUrl.pathname.startsWith("/_vf_modules/")
) {
nestedFetches.push(requestUrl.href);
return await serve(request, projectDir);
}
if (requestUrl.origin === "https://1.1.1.1") {
Expand All @@ -2486,15 +2484,9 @@ 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!);
assertEquals(childUrl.searchParams.get("ssr"), "true");
assertEquals(childUrl.searchParams.get("pins"), snapshot.cacheKey);
}
const ssrParentCode = await ssrParentResponse.text();
assertStringIncludes(ssrParentCode, absolutePath);
assertStringIncludes(ssrParentCode, protocolPath);
},
);
} finally {
Expand Down Expand Up @@ -2971,10 +2963,9 @@ describe({ name: "serveModule", sanitizeResources: false, sanitizeOps: false },
`/_vf_modules/_pins/${encodedSnapshot}/_pins/project-dir/bar.js`,
]
) {
assertEquals(
(await serve(new Request(`http://localhost:3000${path}`), projectDir)).status,
200,
);
const response = await serve(new Request(`http://localhost:3000${path}`), projectDir);
assertEquals(response.status, 409);
assertEquals(response.headers.get("cache-control"), "no-store");
}

const unknown = await serve(
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 @@ -289,6 +293,7 @@ export class LayoutApplicator {
this.dependencyPinningSource,
this.requestUrl?.origin,
this.config,
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 @@ -35,6 +35,7 @@ export function applyLayoutsESM(
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
config?: VeryfrontConfig,
isLocalProject?: boolean,
): Promise<BundledReact.ReactElement> {
return withSpan(
SpanNames.LAYOUT_APPLY_LAYOUTS_ESM,
Expand Down Expand Up @@ -86,6 +87,7 @@ export function applyLayoutsESM(
dependencyPinningSource,
moduleServerOrigin,
config,
isLocalProject,
),
spanAttrs,
);
Expand Down Expand Up @@ -149,6 +151,7 @@ export function applyLayoutsESM(
dependencyPinningSource,
moduleServerOrigin,
config,
isLocalProject,
),
{ "layout.kind": "mdx", "layout.type": "named" },
);
Expand Down
13 changes: 7 additions & 6 deletions src/rendering/layouts/utils/component-loader.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import {
shouldUnwrapAppRouterDocumentLayout,
unwrapAppRouterDocumentLayout,
} from "./component-loader.ts";
import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts";
import { type MDXLoadModuleOptions, mdxRenderer } from "#veryfront/transforms/mdx/index.ts";
import type { MdxBundle } from "#veryfront/types";
import type { RuntimeAdapter } from "#veryfront/platform/adapters/base.ts";
import { hashString } from "#veryfront/cache/hash.ts";
Expand Down Expand Up @@ -495,12 +495,13 @@ describe("rendering/layouts/utils/component-loader", () => {
let moduleReactVersion: unknown;
let modulePinKey: unknown;
let moduleDependencies: unknown;
mutableRenderer.loadModuleESM = ((...args: unknown[]) => {
moduleReactVersion = args[6];
modulePinKey = args[7];
moduleDependencies = args[8];
mutableRenderer.loadModuleESM = (_compiledProgramCode, options) => {
const loadOptions = options as MDXLoadModuleOptions | undefined;
moduleReactVersion = loadOptions?.reactVersion;
modulePinKey = loadOptions?.dependencyPinningCacheKey;
moduleDependencies = loadOptions?.dependencyPinningDependencies;
return Promise.resolve({ default: () => null });
}) as typeof mdxRenderer.loadModuleESM;
};

try {
await loadMDXLayout(
Expand Down
11 changes: 8 additions & 3 deletions src/rendering/layouts/utils/component-loader.ts
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ export function loadMDXLayout(
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
config?: VeryfrontConfig,
isLocalProject?: boolean,
): Promise<BundledReact.ComponentType<{ components?: MDXComponents }> | undefined> {
return withSpan(
SpanNames.LAYOUT_LOAD_MDX,
Expand All @@ -372,8 +373,7 @@ export function loadMDXLayout(
codeLength: code.length,
});

const mod = (await mdxRenderer.loadModuleESM(
code,
const mod = (await mdxRenderer.loadModuleESM(code, {
adapter,
projectId,
projectDir,
Expand All @@ -384,7 +384,8 @@ export function loadMDXLayout(
dependencyPinningDependencies,
dependencyPinningSource,
moduleServerOrigin,
)) as MDXModule;
isLocalProject,
})) as MDXModule;

loadMdxLayoutLog.debug("loadModuleESM DONE", {
projectSlug,
Expand Down Expand Up @@ -415,6 +416,7 @@ export async function preloadMDXLayoutModule(
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
config?: VeryfrontConfig,
isLocalProject?: boolean,
): Promise<void> {
await loadMDXLayout(
bundle,
Expand All @@ -430,6 +432,7 @@ export async function preloadMDXLayoutModule(
dependencyPinningSource,
moduleServerOrigin,
config,
isLocalProject,
);
}

Expand Down Expand Up @@ -517,6 +520,7 @@ export async function applyMDXLayout(
dependencyPinningSource?: DependencyPinningSourceInput,
moduleServerOrigin?: string,
config?: VeryfrontConfig,
isLocalProject?: boolean,
): Promise<BundledReact.ReactElement> {
const React = await getProjectReact(reactVersion);
const LayoutFn = await loadMDXLayout(
Expand All @@ -533,6 +537,7 @@ export async function applyMDXLayout(
dependencyPinningSource,
moduleServerOrigin,
config,
isLocalProject,
);

if (!LayoutFn) {
Expand Down
50 changes: 48 additions & 2 deletions src/rendering/orchestrator/layout.test.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
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 { LayoutOrchestrator } from "./layout.ts";
import { createLayoutComponentCache } from "../layouts/utils/component-loader.ts";
import type { LayoutCollector, LayoutCompiler } from "../layouts/index.ts";
import { createLayoutComponentCache } from "#veryfront/rendering/layouts/utils/component-loader.ts";
import type { LayoutCollector, LayoutCompiler } from "#veryfront/rendering/layouts/index.ts";
import { mdxRenderer } from "#veryfront/transforms/mdx/index.ts";
import { validateVeryfrontConfig } from "#veryfront/config";
import {
Expand Down Expand Up @@ -103,4 +104,49 @@ describe("rendering/orchestrator/layout", () => {
clearImportMapCache();
}
});

it("threads the trusted local-project identity through MDX layout preloading", async () => {
clearImportMapCache();
const projectDir = "/<PROJECT_DIR>";
const originalLoadModuleESM = mdxRenderer.loadModuleESM;
const mutableRenderer = mdxRenderer as unknown as {
loadModuleESM: typeof mdxRenderer.loadModuleESM;
};
let observedIsLocalProject: unknown;
mutableRenderer.loadModuleESM = (_compiledProgramCode, options) => {
observedIsLocalProject = (options as { isLocalProject?: unknown } | undefined)
?.isLocalProject;
return Promise.resolve({ default: () => null });
};

const orchestrator = new LayoutOrchestrator({
projectDir,
projectId: "local-project",
projectSlug: "local-project",
contentSourceId: "local-main",
adapter: createMockAdapter(),
config: validateVeryfrontConfig({ react: { version: "19.1.1" } }),
mode: "development",
layoutCollector: {} as LayoutCollector,
layoutCompiler: {} as LayoutCompiler,
layoutCache: createLayoutComponentCache(),
componentRegistry: {},
isLocalProject: true,
});
const layouts = [{
kind: "mdx",
path: `${projectDir}/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;
clearImportMapCache();
}
});
});
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 @@ -255,6 +257,7 @@ export class LayoutOrchestrator {
dependencyPinningSource,
moduleServerOrigin,
this.config.config,
this.config.isLocalProject === true,
);
return { type: "mdx" as const, path: layout.path, success: true };
} catch (error) {
Expand Down Expand Up @@ -382,6 +385,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
Loading
Loading