Conversation
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
🦋 Changeset detectedLatest commit: 3c6810c The changes in this PR will be included in the next version bump. This PR includes changesets to release 16 packages
Not sure what this means? Click here to learn what changesets are. Click here if you're a maintainer who wants to add another changeset to this PR |
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
Signed-off-by: Trevor Taubitz <trevor.taubitz@flocksafety.com>
|
@terrabitz Thank you for raising this PR. It would be awesome to get LFS support - I initially committed all the images in my project using Git LFS, then discovered that Keystatic Cloud doesn't support reading from LFS either, so it just shows an empty image. While you've already solved the problem, I thought I would just leave my (AI) investigation notes here for clarity. ProblemImages stored in the repository under Git LFS do not display in the Keystatic editor when using Root causeIn cloud mode, the Keystatic editor fetches image blobs from The browser receives this text, attempts to use it as image data, and fails silently — no console error, just a broken image.
Why this is a problem in practiceNewly uploaded images added via the Keystatic editor itself are not tracked by LFS (they are committed via the GitHub API, which bypasses LFS). So a project that uses LFS for migrated/existing images ends up with a mixed state: old images are broken in the editor, new ones work fine — which is confusing and hard to diagnose. Suggested fixWhen fetching a blob from |
Overview
This PR adds Git LFS support to the GitHub mode adapter. That way we can more easily add and version images to repos without cluttering up the Git history. It's enabled via a new opt-in flag.
Note
I am a bit of a noob with React sites, and did use LLM-powered tooling to assist with this PR. I have reviewed all the output and tested it to the best of my ability, but I definitely may have missed something. I feel like I have a decent understanding of all the components I touched, but I don't know what I don't know.
Background
I love Git LFS, and use it pretty much any large binaries so I don't bloat my Git history. I recently setup a Keystatic+Astro site, and was using Git LFS for some of my website image assets. But once it came to adding images via Keystatic, I started to see errors that looked like this:
Even though I had setup LFS and .gitattributes correctly, the GitHub content API that Keystatic was using didn't automatically support LFS. This lead to some images not getting LFS tracked, even though most were.
It looks like the community has been wanting cloud image assets support for a while (#491). It looks like the paid Keystatic Cloud option supports this with a nice image processing pipeline through Cloudflare. But I suspect for a lot of people, Git LFS would be a nice middle ground so that all the assets are tracked in Git via pointer files. GitHub offers a free tier of 10 GB LFS storage and more for paid users, which is probably enough for most people's purposes.
Implementation Notes
LFS support can be enabled via a new opt-in config called
lfsstorage: { kind: 'github', repo: { owner: REPO_OWNER, name: REPO_NAME, }, + lfs: true, }When enabled, Keystatic will gather information about what LFS patterns are configured in the .gitattributes. It does so via a new
LfsPatternsContext.Providerin data.tsx. It looks at a file in the target repo/commit called.gitattributes, and parses it to determine which patterns have been added vialfs track. For now, I made the behavior fairly strict, such that it'll only use patterns wherefilter,diff, andmergeare all set to LFS.Once we have these patterns, that affects the upload path: it checks for the existence of these patterns and the LFS config, and will process any matching files through a separate LFS path. Instead of directly uploading these to the GitHub API, it instead uses a new backend API called
github/lfs/uploadthat uses the LFS basic transfer API to upload to LFS and commit the pointer file.Note
We needed a new backend here because the LFS APIs in GitHub don't have any CORS headers set
The download path works slightly differently: it looks at the content of each images, and determines which ones are actually an LFS pointer file. It does so by looking at the first few bytes to see if they match
version https://git-lfs.github.com/spec/v1. If it is a pointer file, then we resolve it through thegithub/lfs/downloadendpoint. Once resolved, we override the existing cache entry to hold the resolved image bytes instead of just the pointer. That way subsequent pulls can use the image cache directly without having to re-resolve them.Design Notes
There were a few design decisions I made here:
lfsflag. That way existing sites should not be affected at all by the new code paths. Realistically we don't need a new flag, since the existence of a.gitattributesfile with LFS entries would also be a pretty good signal. But I opted to do so to stay as conservative as possible.github/lfs/downloadendpoint does single file downloads, while thegithub/lfs/uploadendpoint does all writes in one batch. The former is good to make sure the images are all downloaded at the same time, so that the initial resolution is as fast as possible. Downside is this is unbounded, so I don't know how it'll perform if we have hundreds of images, each making its own API call.lfs:trueconfig flag being set. It will always resolve LFS files regardless of this setting. This allows both forward and backwards compatibility: you can use non-LFS files in LFS mode, and vise-versa. The LFS mode only affects how files are uploaded. This makes it more robust, since you can enable and disable the LFS setting without losing any images you have previously uploaded.Testing Notes
Apart from a few small unit tests I added for some of the new parsing logic, most of my testing was manual. I also didn't see a whole lot of end-to-end testing, so I opted not to write any. I tested in both a public and private repository to confirm there weren't any differences wrt authentication.
All of this is tested through a minimal Keystatic+Astro+Cloudflare site
After enabling LFS mode, previously-uploaded non-LFS files are still resolved
Adding LFS-managed files works when
lfs: trueis set, and we have our .gitattributes setupAdding non-LFS managed files still works
After disabling LFS mode, the previously-uploaded LFS files are still resolved, while new files don't go through LFS