Skip to content

feat(provider): add TruoCloud provider - #2304

Open
ramphy wants to merge 2 commits into
nuxt:mainfrom
ramphy:add-truocloud-provider
Open

feat(provider): add TruoCloud provider#2304
ramphy wants to merge 2 commits into
nuxt:mainfrom
ramphy:add-truocloud-provider

Conversation

@ramphy

@ramphy ramphy commented Aug 5, 2026

Copy link
Copy Markdown

Adds a provider for TruoCloud.

Images are served from https://img.truo.cloud/i/<pid>/<path> with the
transformation as query parameters, so the configuration is a single baseURL
holding the endpoint the console publishes:

export default defineNuxtConfig({
  image: {
    truocloud: { baseURL: 'https://img.truo.cloud/i/<pid>' }
  }
})

No API key: the delivery contract is public by design, and signing — when a
tenant turns it on — happens server-side.

Three details that are not obvious, each with a test

Booleans travel as 1. createOperationsGenerator stringifies true as
'true', which the service accepts, but every other builder of this contract
emits 1 — and two spellings of one request are two CDN cache entries for the
same image.

A src that is already a TruoCloud URL is rewritten, not wrapped again.
Nuxt re-runs the provider over whatever src it is given, and a partially
migrated site hands it URLs that already point at the CDN. Wrapping twice
produces /i/<pid>/https%3A//img.truo.cloud/i/<pid>/… — a URL that works, costs
twice and is unreadable in a bug report.

Merging the two vocabularies is why the key map is a named const rather than
inline: the URL carries wire names (w) while modifiers carry standard ones
(width), so one side has to be translated before the merge or ?w=200&w=800
comes out meaning whichever the service reads first.

Commas stay literal. The transformation engine does not decode %2C, so an
escaped crop=60,30,0,0 is ignored — the image comes back uncropped, with a
200.

Registered

src/provider.ts, playground/app/providers.ts, playground/nuxt.config.ts,
the six rows in test/providers.ts, a block in test/nuxt/providers.test.ts,
docs/content/3.providers/truocloud.md and the index. Purely additive.

Verified

  • nuxt prepare playground && vitest run test/unit test/nuxt — 174 passed
    (including provider-coverage, which enforces the playground entry and the
    table row)
  • eslint . clean
  • both playground sample URLs render, and the format: 'auto' one really does
    negotiate avif rather than just changing a header

The provider is also run against a shared fixture of URL vectors generated by
the service itself, which is how the TypeScript, PHP and server-side builders of
this contract are kept byte-identical. That fixture found two of the three
issues above while this file was being written.

TruoCloud serves images from https://img.truo.cloud/i/<pid>/<path>, with the
transformation as query parameters, so the provider is a `baseURL` and a key
map.

Three details are not obvious from the URL shape and each has a test:

  - booleans travel as `1`. `createOperationsGenerator` stringifies `true` as
    'true', which the service accepts, but every other builder of this contract
    emits `1` — and two spellings of one request are two CDN cache entries for
    the same image.
  - a `src` that is already a TruoCloud URL is rewritten in place rather than
    wrapped again. Nuxt re-runs the provider over whatever src it is given, and
    a partially migrated site hands it URLs that already point at the CDN;
    wrapping twice produces a URL that works and costs twice.
  - parameters come out sorted, and commas stay literal. The transformation
    engine does not decode %2C, so an escaped crop=60,30,0,0 is ignored and the
    image comes back uncropped, with a 200.

Merging the two vocabularies is why the key map is a named const: a URL carries
wire names (`w`) while modifiers carry standard ones (`width`), so one side is
translated before the merge or `?w=200&w=800` comes out meaning whichever the
service reads first.

Registered in src/provider.ts, the playground, the provider test table and the
docs. 174 tests pass; eslint clean.
@ramphy
ramphy requested a review from danielroe as a code owner August 5, 2026 03:42
@pkg-pr-new

pkg-pr-new Bot commented Aug 5, 2026

Copy link
Copy Markdown

Open in StackBlitz

npm i https://pkg.pr.new/@nuxt/image@2304

commit: 14cc8e6

@coderabbitai

coderabbitai Bot commented Aug 5, 2026

Copy link
Copy Markdown

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: b4353e9e-6003-4041-95d7-fa3b94af1db7

📥 Commits

Reviewing files that changed from the base of the PR and between 6a44d69 and 14cc8e6.

📒 Files selected for processing (1)
  • test/e2e/__snapshots__/truocloud.json5

📝 Walkthrough

Walkthrough

Adds the TruoCloud image provider with modifier and format mappings, path encoding, query canonicalization, existing-URL handling, and merged modifiers. Registers the provider as built in. Adds URL fixture and Nuxt provider tests. Adds provider documentation, homepage listing, and playground examples with demo configuration.

Estimated code review effort: 3 (Moderate) | ~25 minutes

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and concisely identifies the addition of the TruoCloud provider.
Description check ✅ Passed The description directly explains the TruoCloud provider, its configuration, behavior, registration, tests, and documentation.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 2

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@src/runtime/providers/truocloud.ts`:
- Around line 164-166: Update the direct-source path handling around the
existing `path` assignment and `encodePath` so valid `%HH` percent escapes are
preserved instead of being encoded again, while still encoding unescaped path
characters and retaining the verbatim `existing.path` behavior. Add a regression
fixture covering a source path such as `/assets/a%20b.png` and verify the
generated URL references the original encoded asset.
- Around line 158-160: Update the getImage baseURL handling in the TruoCloud
provider to require a PID-qualified baseURL at runtime, or replace the current
default with a valid /i/<pid> endpoint. Ensure generated URLs always target the
required PID-qualified delivery path when callers omit configuration.
🪄 Autofix

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: d7334603-f194-4937-8f94-897dcb62a9e1

📥 Commits

Reviewing files that changed from the base of the PR and between ff4910f and 6a44d69.

📒 Files selected for processing (8)
  • docs/content/3.providers/truocloud.md
  • docs/content/index.md
  • playground/app/providers.ts
  • playground/nuxt.config.ts
  • src/provider.ts
  • src/runtime/providers/truocloud.ts
  • test/nuxt/providers.test.ts
  • test/providers.ts

Comment on lines +158 to +160
export default defineProvider<TruoCloudOptions>({
getImage: (src, { modifiers, baseURL = 'https://img.truo.cloud' }) => {
const existing = unwrap(src, baseURL)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

Require a PID-qualified baseURL.

The default value produces https://img.truo.cloud/<path>. It does not produce the required /i/<pid>/<path> endpoint. A caller that selects this provider without configuration gets an invalid delivery URL.

Require baseURL at runtime, or provide a valid PID-qualified default.

Proposed fix
 export default defineProvider<TruoCloudOptions>({
-  getImage: (src, { modifiers, baseURL = 'https://img.truo.cloud' }) => {
+  getImage: (src, { modifiers, baseURL }) => {
+    if (!baseURL) {
+      throw new Error('The TruoCloud provider requires a baseURL such as https://img.truo.cloud/i/<pid>.')
+    }
     const existing = unwrap(src, baseURL)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
export default defineProvider<TruoCloudOptions>({
getImage: (src, { modifiers, baseURL = 'https://img.truo.cloud' }) => {
const existing = unwrap(src, baseURL)
export default defineProvider<TruoCloudOptions>({
getImage: (src, { modifiers, baseURL }) => {
if (!baseURL) {
throw new Error('The TruoCloud provider requires a baseURL such as https://img.truo.cloud/i/<pid>.')
}
const existing = unwrap(src, baseURL)
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/providers/truocloud.ts` around lines 158 - 160, Update the
getImage baseURL handling in the TruoCloud provider to require a PID-qualified
baseURL at runtime, or replace the current default with a valid /i/<pid>
endpoint. Ensure generated URLs always target the required PID-qualified
delivery path when callers omit configuration.

Comment on lines +164 to +166
// An already-encoded path is reused verbatim; encoding it again would turn
// `%20` into `%2520`.
const path = existing ? existing.path : encodePath(src)

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Preserve valid percent escapes for direct source paths.

A direct source such as /assets/a%20b.png does not enter existing. encodePath changes it to assets/a%2520b.png. The generated URL then requests a different asset.

Preserve existing %HH escapes when encoding direct source segments. Add a regression fixture for an encoded path.

Proposed fix
     .map(segment =>
       encodeURIComponent(segment).replace(
         /[!'()*]/g,
         c => `%${c.charCodeAt(0).toString(16).toUpperCase()}`,
-      ),
+      ).replace(/%25([0-9A-Fa-f]{2})/g, (_, hex: string) => `%${hex.toUpperCase()}`),
     )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/runtime/providers/truocloud.ts` around lines 164 - 166, Update the
direct-source path handling around the existing `path` assignment and
`encodePath` so valid `%HH` percent escapes are preserved instead of being
encoded again, while still encoding unescaped path characters and retaining the
verbatim `existing.path` behavior. Add a regression fixture covering a source
path such as `/assets/a%20b.png` and verify the generated URL references the
original encoded asset.

Adding a playground entry creates a new snapshot case in both e2e suites, and
without the file they fail on a mismatch against nothing. Generated by running
the real browser tests, not written by hand — the snapshot records the URLs
Chromium actually requested, and guessing them is how it would drift.

It also happens to be the clearest proof the provider works end to end: the
requests it captured are the canonical, sorted form.
@codecov-commenter

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.
✅ Project coverage is 32.61%. Comparing base (de96294) to head (14cc8e6).
⚠️ Report is 3 commits behind head on main.

Additional details and impacted files
@@           Coverage Diff           @@
##             main    #2304   +/-   ##
=======================================
  Coverage   32.61%   32.61%           
=======================================
  Files           7        7           
  Lines         371      371           
  Branches      131      131           
=======================================
  Hits          121      121           
  Misses        194      194           
  Partials       56       56           

☔ View full report in Codecov by Harness.
📢 Have feedback on the report? Share it here.

🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants