Skip to content

fix: load pierre themes without json module imports#45869

Merged
gumadeiras merged 5 commits intoopenclaw:mainfrom
NickHood1984:codex/fix-diffs-ci
Mar 28, 2026
Merged

fix: load pierre themes without json module imports#45869
gumadeiras merged 5 commits intoopenclaw:mainfrom
NickHood1984:codex/fix-diffs-ci

Conversation

@NickHood1984
Copy link
Copy Markdown
Contributor

Summary

  • preload the bundled pierre themes without relying on JSON module imports
  • override the pre-registered theme entries used by @pierre/diffs so the extension can resolve them on newer Node builds
  • keep the change scoped to extensions/diffs

Why

The checks (node, extensions, pnpm test:extensions) failure inherited by #40791 includes extensions/diffs tests breaking when Node requires type: "json" for those bundled theme imports. This PR isolates that fix so it can be reviewed independently.

Validation

  • pnpm exec vitest run --config vitest.extensions.config.ts extensions/diffs/index.test.ts extensions/diffs/src/render.test.ts extensions/diffs/src/tool.test.ts

@dsantoreis

This comment was marked as spam.

@dsantoreis

This comment was marked as spam.

@greptile-apps
Copy link
Copy Markdown
Contributor

greptile-apps bot commented Mar 14, 2026

Greptile Summary

This PR extracts pierre theme loading into a new pierre-themes.ts module and fixes the extensions/diffs test failures caused by Node's stricter JSON module import requirements. Instead of using import … assert { type: "json" }, it reads the theme JSON files at runtime via fs.readFile and re-registers them in @pierre/diffs's internal RegisteredCustomThemes map, also clearing any stale entries in ResolvedThemes and ResolvingThemes before the next render.

  • pierre-themes.ts (new): Singleton ensurePierreThemesRegistered() function that, on first call, registers lazy loaders for pierre-light and pierre-dark themes by finding @pierre/theme as a pnpm-layout sibling of @pierre/diffs and reading the JSON files with fs.readFile.
  • render.ts: Adds await ensurePierreThemesRegistered() at the top of renderBeforeAfterDiff and renderPatchDiff, ensuring theme loaders are in place before preloadFileDiff / preloadMultiFileDiff run.
  • Behavior change: The previous implementation (patchPierreThemeLoadersForNode24) wrapped path resolution in a try/catch and explicitly fell back to upstream loaders on any error. The new loadPierreTheme has no equivalent guard; a missing theme file will propagate as a render-time error rather than a silent fallback.

Confidence Score: 3/5

  • PR is safe to merge in a pnpm environment but removes the silent-fallback safety net from the previous implementation.
  • The core fix — using fs.readFile instead of JSON module imports — is sound and the pnpm path derivation mirrors what the old createRequire-based resolver produced. The one functional regression is the removal of the try/catch fallback that the previous code explicitly included to keep upstream loaders on path-resolution failures. Without it, any environment where @pierre/diffs isn't resolvable at the expected relative path will receive a render error instead of graceful degradation.
  • extensions/diffs/src/pierre-themes.ts — the loadPierreTheme function lacks the error fallback that the previous implementation intentionally provided.
Prompt To Fix All With AI
This is a comment left during a code review.
Path: extensions/diffs/src/pierre-themes.ts
Line: 9-16

Comment:
**Missing error fallback removed from old implementation**

The previous implementation wrapped path resolution and theme loader registration in a `try/catch` with an explicit fallback comment: `// Keep upstream loaders if theme files cannot be resolved.` That guard meant a broken theme install never caused a render failure — it gracefully degraded to the upstream loaders.

In the new code, `loadPierreTheme` calls `fs.realpath()` and `fs.readFile()` without any catch. If the resolved `@pierre/diffs` symlink doesn't exist at the expected relative location (e.g., in a flat `node_modules` or a different package manager), `fs.realpath` will throw `ENOENT`. Because `loadPierreTheme` is registered as a lazy callback in `RegisteredCustomThemes`, the error won't surface during `ensurePierreThemesRegistered()` — it will surface later as an uncaught rejection inside `preloadFileDiff` / `preloadMultiFileDiff`, causing every render to fail rather than falling back.

Consider wrapping the body of `loadPierreTheme` (or the entire registration callback) in a try/catch so that on resolution failure the slot is removed from `RegisteredCustomThemes` and `@pierre/diffs` retries with its own upstream loader:

```ts
async function loadPierreTheme(themeFileName: string, themeName: string): Promise<unknown> {
  try {
    const diffsPackageRoot = await fs.realpath(
      fileURLToPath(new URL("../node_modules/@pierre/diffs", import.meta.url)),
    );
    const themePath = path.join(diffsPackageRoot, "..", "theme", "themes", themeFileName);
    return {
      ...(JSON.parse(await fs.readFile(themePath, "utf8")) as Record<string, unknown>),
      name: themeName,
    };
  } catch {
    // Fall back to upstream loaders if theme files cannot be resolved.
    return undefined;
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

Last reviewed commit: 09e2e5f

Comment on lines +9 to +16
const diffsPackageRoot = await fs.realpath(
fileURLToPath(new URL("../node_modules/@pierre/diffs", import.meta.url)),
);
const themePath = path.join(diffsPackageRoot, "..", "theme", "themes", themeFileName);
return {
...(JSON.parse(await fs.readFile(themePath, "utf8")) as Record<string, unknown>),
name: themeName,
};
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Missing error fallback removed from old implementation

The previous implementation wrapped path resolution and theme loader registration in a try/catch with an explicit fallback comment: // Keep upstream loaders if theme files cannot be resolved. That guard meant a broken theme install never caused a render failure — it gracefully degraded to the upstream loaders.

In the new code, loadPierreTheme calls fs.realpath() and fs.readFile() without any catch. If the resolved @pierre/diffs symlink doesn't exist at the expected relative location (e.g., in a flat node_modules or a different package manager), fs.realpath will throw ENOENT. Because loadPierreTheme is registered as a lazy callback in RegisteredCustomThemes, the error won't surface during ensurePierreThemesRegistered() — it will surface later as an uncaught rejection inside preloadFileDiff / preloadMultiFileDiff, causing every render to fail rather than falling back.

Consider wrapping the body of loadPierreTheme (or the entire registration callback) in a try/catch so that on resolution failure the slot is removed from RegisteredCustomThemes and @pierre/diffs retries with its own upstream loader:

async function loadPierreTheme(themeFileName: string, themeName: string): Promise<unknown> {
  try {
    const diffsPackageRoot = await fs.realpath(
      fileURLToPath(new URL("../node_modules/@pierre/diffs", import.meta.url)),
    );
    const themePath = path.join(diffsPackageRoot, "..", "theme", "themes", themeFileName);
    return {
      ...(JSON.parse(await fs.readFile(themePath, "utf8")) as Record<string, unknown>),
      name: themeName,
    };
  } catch {
    // Fall back to upstream loaders if theme files cannot be resolved.
    return undefined;
  }
}
Prompt To Fix With AI
This is a comment left during a code review.
Path: extensions/diffs/src/pierre-themes.ts
Line: 9-16

Comment:
**Missing error fallback removed from old implementation**

The previous implementation wrapped path resolution and theme loader registration in a `try/catch` with an explicit fallback comment: `// Keep upstream loaders if theme files cannot be resolved.` That guard meant a broken theme install never caused a render failure — it gracefully degraded to the upstream loaders.

In the new code, `loadPierreTheme` calls `fs.realpath()` and `fs.readFile()` without any catch. If the resolved `@pierre/diffs` symlink doesn't exist at the expected relative location (e.g., in a flat `node_modules` or a different package manager), `fs.realpath` will throw `ENOENT`. Because `loadPierreTheme` is registered as a lazy callback in `RegisteredCustomThemes`, the error won't surface during `ensurePierreThemesRegistered()` — it will surface later as an uncaught rejection inside `preloadFileDiff` / `preloadMultiFileDiff`, causing every render to fail rather than falling back.

Consider wrapping the body of `loadPierreTheme` (or the entire registration callback) in a try/catch so that on resolution failure the slot is removed from `RegisteredCustomThemes` and `@pierre/diffs` retries with its own upstream loader:

```ts
async function loadPierreTheme(themeFileName: string, themeName: string): Promise<unknown> {
  try {
    const diffsPackageRoot = await fs.realpath(
      fileURLToPath(new URL("../node_modules/@pierre/diffs", import.meta.url)),
    );
    const themePath = path.join(diffsPackageRoot, "..", "theme", "themes", themeFileName);
    return {
      ...(JSON.parse(await fs.readFile(themePath, "utf8")) as Record<string, unknown>),
      name: themeName,
    };
  } catch {
    // Fall back to upstream loaders if theme files cannot be resolved.
    return undefined;
  }
}
```

How can I resolve this? If you propose a fix, please make it concise.

@gumadeiras gumadeiras self-assigned this Mar 27, 2026
@gumadeiras gumadeiras force-pushed the codex/fix-diffs-ci branch 2 times, most recently from f3904c2 to 0fa2d8e Compare March 28, 2026 00:11
@gumadeiras gumadeiras merged commit 29674d7 into openclaw:main Mar 28, 2026
7 checks passed
@gumadeiras
Copy link
Copy Markdown
Member

Merged via squash.

Thanks @NickHood1984!

w-sss pushed a commit to w-sss/openclaw that referenced this pull request Mar 28, 2026
Merged via squash.

Prepared head SHA: dd456aa
Co-authored-by: NickHood1984 <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
johnkhagler pushed a commit to johnkhagler/openclaw that referenced this pull request Mar 29, 2026
Merged via squash.

Prepared head SHA: dd456aa
Co-authored-by: NickHood1984 <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
alexcode-cc pushed a commit to alexcode-cc/clawdbot that referenced this pull request Mar 30, 2026
Merged via squash.

Prepared head SHA: dd456aa
Co-authored-by: NickHood1984 <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
alexjiang1 pushed a commit to alexjiang1/openclaw that referenced this pull request Mar 31, 2026
Merged via squash.

Prepared head SHA: dd456aa
Co-authored-by: NickHood1984 <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
livingghost pushed a commit to livingghost/openclaw that referenced this pull request Mar 31, 2026
Merged via squash.

Prepared head SHA: dd456aa
Co-authored-by: NickHood1984 <[email protected]>
Co-authored-by: gumadeiras <[email protected]>
Reviewed-by: @gumadeiras
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants