Repository navigation
Add optimole url: print assets' CDN URLs, resized or converted - #2
Merged
Merged
Conversation
internal/cli/optimole-export-1/ (an export.json and a lock file) was left by a test run during the export work and committed with it. No test writes there any more. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GetAsset calls GET /api/assets/{key}, which answers with the asset's CDN
URL, MIME type and original size. The dashboard answers 403 both for an
unknown key and for an asset a folder-scoped key may not see. The fake
server serves the route the same way, and AddAsset puts a finished asset
in its library.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Splits a delivery URL into its options and source (id:HASH/... or an origin URL), following the CDN's viewer-request function: only the first copy of an option counts, and options are applied in a fixed order, which String writes them in. Check refuses values the CDN answers with an empty image (w, h, q), ignores (a cache buster without a dot) or resets (an unknown gravity). AVIF cannot be forced, because the CDN drops f:avif, and the resize types are the dashboard's (rt:crop answers a redirect). Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Takes asset keys, Optimole URLs, or the JSON lines of upload --json and url --json (from stdin when there are no arguments), applies --width, --height, --resize, --gravity, --focus, --quality, --format, --dpr, --download, --set, --unset and --reset, and prints one URL per input in input order. An option already in the URL is replaced, never repeated. URLs need no API call and no key; keys are looked up in parallel. A failed input is reported and the others are still printed; a rejected key stops the run. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Ctrl-C now stops a run waiting on an idle stdin pipe. - A URL with spaces or control characters is refused, so a newline cannot turn one printed URL into two; messages quote control characters. - Messages hide the API keys in use, and a line that looks like an API key is not echoed. - A URL whose origin path holds a segment the CDN reads as an option (w:9 with no w: of its own) is refused: the CDN would misread it. - Raw values match what the CDN reads: fp:X:Y with colons, cache busters without a colon, finite coordinates. - A custom-domain URL whose path starts with the origin parses, so a URL left without options can be piped back. - api.IsAssetKey replaces a copy of the key pattern; dead code and narrating comments removed. Tests cover the lookup limit, an idle pipe, output failures, newline injection and key redaction, and the fuzz test edits the URLs it reparses. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Usage errors hide the API keys in use, and a long input line is shortened only after its keys are hidden, so no part of a key shows. - Validate follows the CDN's literal rules: it deletes the first /ig:avif and /f:avif in the path, and reads no other ig value. - Before an origin URL, the segments the CDN removes are dropped, so a custom-domain URL left without options pipes back. - The dispatcher stops waiting for stdin when the run ends. - gofmt on the test file, which CI caught. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
- Validate works on the written URL like the CDN: it refuses a URL whose first /ig:avif or /f:avif (deleted as text, not as a segment) falls in the origin path, or whose origin path alone holds ig:avif, which turns AVIF off. Checked against the viewer-request function on 7 URLs. - Before an origin URL, a segment holding ig:avif is kept: dropping it would turn AVIF back on. - The flag package's own errors (a bad -c or --json value) hide the keys known before the flags are read. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The flag package's errors were redacted with the environment and saved keys only, so --api-key K -c K still printed K. The --api-key value is now read from the arguments before they are parsed. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Reading --api-key before parsing missed keys in several cases (a value taken by another flag, a repeated --api-key, a saved key next to a flag key). The flag package only shows a value inside quotes in its "invalid value" errors, so those messages now leave the value out: no key can reach them, whatever its source. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
…rt test - The flag package also repeats a whole argument (bad flag syntax: ---api-key=K) and an unknown flag's name. The first now shows no argument, and a name that looks like an API key is not shown. - export tests: the fake records a download when it ends, and a stalled one can end after its retry (macOS CI did so), so the downloads helper now orders requests by when they started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
A pasted argument can hold spaces or newlines ("--api-key KEY" as one
argument, a key on a second line). Flag errors now drop the whole
argument of bad flag syntax, and name an unknown flag only when it looks
like one (letters, digits and dashes, under 20 characters).
export tests: the stalled-download test waits until the fake has recorded
both requests, since the stalled one is recorded only when its handler
sees the cancel.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Scripts cannot get an asset's CDN URL in another size or format without building the URL by hand. The new
optimole urlcommand builds it from an asset key or an Optimole URL, and reads piped output fromuploadandurl.What changed
optimole urlcommand — takes asset keys, Optimole URLs, or JSON lines from stdin. It prints one URL per input, in input order.Asset keys —
api.GetAssetcallsGET /api/assets/{key}. The answer gives the URL on the account's delivery domain, custom domains included. Up to-clookups run at a time.URL rewriting (
internal/imgurl) — reads a URL the way the CDN does. The command replaces an option that is already in the URL, and writes options in the CDN's order.Value checks — the command refuses values that the CDN ignores or answers with an empty image. Examples: a bad
w,horq, acb:without theabc.1form, an unknown option name.Unsafe input — the command refuses URLs with spaces or control characters, and URLs that the CDN misreads because of their origin path. Messages and flag errors do not show API keys.
Errors — a failed input does not stop the other inputs (exit 1). A missing or rejected API key stops the run at the first key. File URLs print without change, and image flags are an error for them.
Note
The rules come from the CDN's CloudFront viewer-request function,
viewerRequest.jsinoptimole-lambda-functions. It uses only the first copy of a repeated option, so a secondw:does nothing. It also removesf:avifand selects the format from the browser'sAcceptheader. For this reason--format avifis refused, and--format autogives AVIF to browsers that accept it.rt:crop(in the PHP SDK) answers a 302 to the original image, so--resizetakes the dashboard's five types only. In a live check,f:bestgave PNG to a browser that accepts AVIF, sobestis not offered.Note
The PR also has two small changes from #1. It removes
internal/cli/optimole-export-1/, two files that a test run left and #1 committed by mistake. It also stops a timing flake inTestRunRestartsAStalledDownload, which failed once on macOS CI. Folder-scoped (optml-s) keys are tested only against the fake server, not the real dashboard.Command flow
flowchart TB subgraph Input["1 · Input"] A["Argument, or a stdin line"]:::entry B{"Asset key, URL<br/>or JSON line?"}:::decision S["Skipped"]:::stopped A --> B B -- "Blank, or an<br/>upload summary" --> S end subgraph Resolve["2 · Resolve"] C["New: GET /api/assets/{key}<br/>up to -c at a time"]:::work D["New: Read the URL<br/>the way the CDN does"]:::work E["New: Replace options<br/>from the flags"]:::work F{"New: Image, and safe<br/>for the CDN?"}:::decision C -- "Found" --> D D --> E --> F end subgraph Output["3 · Output, in input order"] G["URL on stdout,<br/>or a --json line"]:::stored H["Error for this input;<br/>the next inputs continue"]:::stopped I["Run stops"]:::stopped end B -- "32 hex characters" --> C B -- "https:// URL, or JSON<br/>with a url" --> D B -- "Other" --> H C -- "403, 404 or<br/>other failure" --> H C -- "401, or no API key" --> I F -- "Yes" --> G F -- "No" --> H style Input fill:#eff6ff,stroke:#93c5fd,color:#172554 style Resolve fill:#f0fdf4,stroke:#86efac,color:#14532d style Output fill:#faf5ff,stroke:#c4b5fd,color:#3b0764 classDef entry fill:#dbeafe,color:#172554,stroke:#2563eb,stroke-width:2px classDef work fill:#1a7f37,color:#fff,stroke:#116329,stroke-width:2px classDef decision fill:#fef3c7,color:#451a03,stroke:#b45309,stroke-width:2px classDef stopped fill:#fee2e2,color:#450a0a,stroke:#dc2626,stroke-width:2px classDef stored fill:#ede9fe,color:#2e1065,stroke:#7c3aed,stroke-width:2pxFlags and URL options
--width N|auto,--height N|autow:,h:--resize fit|fill|fill-down|force|autort:--gravity center|top|…|bottom-right|smart,--focus X:Yg:,g:fp:X:Y--quality auto|mauto|eco|1-100q:--format webp|jpg|png|gif|ico|jxl|heicf:--format autof:--dpr 1-5,--downloaddpr:,att:1--set NAME:VALUE,--unset NAME,--reset--jsonoutputOne object per input. Other commands can read it back.
inputurlkeymime_type,original_width,original_heighterror,codecodeisinvalid_input,not_found,no_urlornot_an_imageQA
You need Go, an Optimole API key, and a JPEG that is at least 640 px wide. To get the key, go to
https://dashboard.optimole.com/settings/api-keys/(Settings → API Keys).Build the branch:
Paste the API key when the command asks for it.
Expect:
./optimole url -hshows theurlflags.Upload the JPEG and pipe the result:
./optimole upload photo.jpg --json | ./optimole url --width 320 --format webpExpect: one URL that contains
/w:320/and/f:webp/.Open that URL in Chrome.
Expect: the tab title shows a width of 320, for example
(320×240).Pipe that URL into the command again:
./optimole url 'URL-FROM-STEP-2' --quality 40Expect: the new URL contains
/q:40/and only one/w:segment.Run
./optimole url 'URL-FROM-STEP-2' --format auto. Open the new URL in Chrome, withDevTools → Networkopen.Expect: the image row shows the type
avif.Request the same URL without AVIF support:
Expect:
image/jpeg.Run
./optimole url 'URL-FROM-STEP-2' --download. Open the new URL in a browser.Expect: the browser downloads the image and does not show it.
🤖 Generated with Claude Code