Skip to content

feat(truocloud): add TruoCloud provider - #197

Open
ramphy wants to merge 1 commit into
ascorbic:mainfrom
ramphy:add-truocloud-provider
Open

feat(truocloud): add TruoCloud provider#197
ramphy wants to merge 1 commit into
ascorbic:mainfrom
ramphy:add-truocloud-provider

Conversation

@ramphy

@ramphy ramphy commented Aug 4, 2026

Copy link
Copy Markdown

Adds a provider for TruoCloud image delivery.

URLs look like https://img.truo.cloud/i/<pid>/<path>?w=800&f=auto, with the
transformation as query parameters. Two modes share the path — a path mapped
onto the tenant's own origin, and /fetch/<encoded url> to proxy a third-party
one — the same shape as imgix's Web Folder and Web Proxy.

Follows CONTRIBUTING.md: provider, test, data/domains.json, deno.jsonc,
demo/src/examples.json, and the four registration points in src/. Purely
additive, no existing file loses a line.

Detection is by domain, not by path

/i/ is far too generic for the shared path registry and would claim URLs that
are not ours, so it is registered in domains.json only. A custom domain
therefore needs an explicit provider, the same as the other providers that
allow them.

Two things that are not obvious from the URL shape

Parameters come out sorted by name. The service accepts any order, but
every builder of this contract sorts them, and two orderings of one request are
two CDN cache entries for the same image.

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. sortSearch undoes the escaping that URLSearchParams applies.

extract drops s and exp

They are an HMAC over that exact path and query. Returning them as operations
would let generate produce a URL carrying a signature that no longer covers
it — a 403 at display time with nothing pointing at the cause.

Verified

  • deno test src/ — 88 passed, 288 steps
  • deno lint and deno fmt --check clean on the new files
  • 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

The demo example URL is live.

TruoCloud serves images from `https://img.truo.cloud/i/<pid>/<path>`, with the
transformation as query parameters. Two modes share the path: a path mapped
onto the tenant's own origin, and `/fetch/<encoded url>` to proxy a third-party
one — the same shape as imgix's Web Folder and Web Proxy.

Detection is by domain rather than path: `/i/` is far too generic for the
shared path registry and would claim URLs that are not ours. A custom domain
therefore needs an explicit provider, as with the other providers that allow
them.

Two details that are not obvious from the URL shape:

  - parameters are emitted sorted by name. The service accepts any order, but
    every builder of this contract sorts them, and two orderings of one request
    are two CDN cache entries for the same image.
  - 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.

`extract` drops `s` and `exp` instead of returning them as operations: they
are an HMAC over that exact path and query, so carrying them through
`generate` would produce a URL whose signature no longer covers it.
@ramphy

ramphy commented Aug 4, 2026

Copy link
Copy Markdown
Author

The red test check is not from this PR — happy to help fix it, but I did not
want to push unrelated changes into a provider PR without asking.

Diagnosis: src/providers/cloudflare.ts:117 on main is a line containing only
a tab, and deno fmt wants it empty. So the deno fmt src step produces a
change on every branch, and git-auto-commit-action then tries to commit it —
which fails with Invalid status code: 128 on a fork PR, because the default
token cannot push to the fork.

That is why earlier fork PRs were green and this one is red: back then
cloudflare.ts was clean, so deno fmt produced nothing and the auto-commit
step exited without doing anything.

Reproduced against main, no changes of mine involved:

$ git clone --depth 1 https://github.com/ascorbic/unpic.git && cd unpic
$ deno fmt --check src
from .../src/providers/cloudflare.ts:
117 |-\t
117 |+
error: Found 1 not formatted file in 66 files

Two options, whichever you prefer: I add the one-character fix to this branch
(it would make the check green here), or you take it separately and I rebase.
My own files are clean — deno fmt --check and deno lint both pass on them,
and deno test src/ is 88 passed / 288 steps locally.

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.

1 participant