Add Reprint pulls as agentic UI beta feature - #4704
Draft
fredrikekelund wants to merge 27 commits into
Draft
Conversation
flat-docroot --force deletes whatever stands where a symlink must go, so a first pull wiped the plugins, themes and uploads that only the local site had. --on-flatten-to-conflict=adopt moves those entries into the fs-root first, so they survive and stay where a later push can read them, and a plugin or theme the pull also carries is left to the pull rather than merged with the local copy. Still first-pull only. A delta re-pull passes no mode at all, keeping the default that refuses to overwrite a live site. Reprint is pinned to the committed phar while these changes are unreleased, so the download step no longer replaces it.
Testing pull-reprint end to end needs all three unreleased Reprint changes at once, and no single branch carries them: #557 preserve-local-content-into-fs-root 02d0ab73 --on-flatten-to-conflict=error|replace|adopt #540 f26d/apply-runtime-db-apply-option ba329f38 --target-* options for apply-runtime #436 adamziel/expose-import-layout-metadata 6dc0141f source layout and artifact status from import-metadata Built from integration/studio-test-build in the reprint worktree, which merges the three cleanly; its Import suite passes at 650 tests. Rebuild by merging those three heads and running composer build:phar.
Reprint's adopt conflict mode now moves the wp-content entries that only the site directory holds into the fs-root, so Studio no longer needs its own pass to do it. The two overlapped on every remaining case: an unselected path, a kept database and a local-only plugin are all simply absent from the fs-root, which is what adopt keys on. Reprint's version is also the more careful one. It stops at whole plugins and themes, where seedEntry() recursed into any directory both sides had and would merge two versions of the same plugin. And it runs where the source of each component is already resolved from preflight, so a detached uploads directory is adopted against the directory it actually came from. The first-pull selective-sync test now asserts what Studio is responsible for — asking for the adopt mode — rather than the file movement a mocked Reprint never performs.
resolveOnlyPathsToAbsolute() existed to tell the local preservation pass which remote paths the selection covered. Nothing asks that question now, so it and its token table go, along with the test that was its only remaining consumer. Also drops a stale line claiming a partial first-pull selection has its unselected local content preserved here; the flatten step keeps it.
#436 and #540 have landed on Reprint's trunk, so the bundled build no longer needs an integration branch carrying three heads. This one is trunk plus the merge-wp-content PR, which replaces #557's --on-flatten-to-conflict with a command of its own: merge-wp-content <url> --state-dir=DIR --fs-root=DIR --from=DIR Built from the merge-wp-content branch in the reprint worktree with composer build:phar; its Import suite passes at 650 tests. Rebuild by checking out that branch and running the same command. The download step in scripts/download-wp-server-files.ts stays disabled while these changes are unreleased.
A first pull onto a site that already has content kept that content by asking flat-docroot to adopt it. Reprint has replaced that mode with a command of its own, so the pull now runs it as a step: merge-wp-content <url> --state-dir --fs-root --from=<site path> It moves the plugins, themes, uploads and anything else only the local wp-content has into the fs-root, so the symlink the flatten then puts in their place still reaches them, and files-push, which reads the fs-root and nothing else, can still see them. Entries the pull already has are left alone, and a plugin or theme both sides have is never merged: keeping the files the pulled version dropped would leave a directory matching no release. The step runs only on a first pull. Afterwards the site's wp-content is a symlink into the fs-root, so it has nothing of its own left to move and the command would find nothing. It has to follow pull-files, whose local index Reprint reads as the record of a finished file pull; run any earlier and every unfetched path looks absent, so local copies move in and the pull writes remote versions over them. flat-docroot goes back to --force on a first pull, exactly as it was before the adopt mode existed. Two consequences worth knowing, both unchanged from adopt. A blank Studio install's wp-content/db.php and wp-content/mu-plugins/ sqlite-database-integration move into the fs-root, which is the tree Reprint indexes and a future files-push would send, even though Studio's own exporter excludes both as local-only. Neither runs: the generated runtime.php pre-sets $wpdb, so require_wp_db() returns before it would require db.php, and WordPress does not auto-load mu-plugin subdirectories. And on a first pull with the database excluded, the live wp-content/database/.ht.sqlite moves too, resolving again through the symlink the flatten places, so there is a window between the two steps where the site's database is not at its path.
Review of the Reprint side turned up two defects, both fixed on the merge-wp-content branch at 3f8020ca and neither visible in Studio's invocation, so this is a phar refresh with no code change beside it. A relative symlink pointing inside the wp-content being merged now keeps its value instead of being recomputed against where its target stood before the run. A child theme pointing at its parent broke as soon as both moved, which a first pull onto a site using one would have hit. And --from is resolved against the working directory when relative. That threw "Path must be absolute" on every run with anything to move. Studio passes the absolute site.path from its own site record, so it never reached this one. Built from the merge-wp-content branch in the reprint worktree with composer build:phar, and checked through the archive itself: a relative --from resolves, an in-tree sibling link keeps its value and finds its parent, and a link pointing out of the tree is still rewritten and still resolves. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Reprint's --from took a site directory and appended wp-content to it. It now takes that directory, so a site which moved WP_CONTENT_DIR merges like a conventional one, and pointing the flag at a wp-content directory no longer succeeds having done nothing. Studio passes metadata.sitePath + wp-content, which is the same path it was resolving to before, so the pull behaves as it did. The bundled Reprint is rebuilt from the branch carrying that change. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A pulled site's wp-content is a symlink into the pull's raw directory, and nothing that looks for symlinks descends through one: `find` needs -L to follow, the Node walk treats a symlinked directory as a leaf, and chokidar runs with followSymlinks off. So the open_basedir scan of the site directory stopped at that link and never saw the plugins and themes behind it, and the watcher, pointed at the same link, saw nothing at all. A theme symlinked into wp-content before a pull survives the pull now, and then fails to load: Warning: is_dir(): open_basedir restriction in effect. File(…/wp-content/themes/koinonia) is not within the allowed path(s) Resolving the directory before scanning it fixes both. On the test site that takes the granted set from 4 entries to 12; seven of the new ones are WP.com's symlinked plugins, which resolve inside the pull directory and were already covered, and the eighth is the theme, which points at ~/Downloads and was not. The scan resolves its own argument, so any caller handing it a symlink is covered. That alone does not reach the reported case: the startup scan is given the site directory, which is real, and the link is one level inside it — hence the second scan naming wp-content. realpath is the native variant for the reason getFullyResolvedTmpDirPath already uses it: these paths become an INI value, and a Windows 8.3 short name carries a tilde that PHP's argument scanner rejects. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…opt-flatten-to-content # Conflicts: # apps/cli/commands/pull-reprint.ts # apps/cli/commands/tests/pull-reprint.test.ts # apps/cli/lib/pull/preserve-local-content.test.ts # apps/cli/lib/pull/preserve-local-content.ts # apps/cli/lib/pull/reprint-selector.test.ts # apps/cli/lib/pull/reprint-selector.ts # apps/cli/php-server-child.ts # scripts/download-wp-server-files.ts
Ports the `Reprint pull engine` beta feature onto the current pull-reprint command. `pullSite` now takes its trailing arguments as an options object carrying the engine, while keeping the cancellation plumbing and the progress `action` passthrough that landed on trunk in the meantime. The pull also reports overall progress as a percentage, exits non-zero when it fails, reinstalls the SQLite integration so phpMyAdmin keeps working, announces a reused content selection when it resumes, and defers marking a site `pulling` until the first step that actually rewrites the site directory. Both front ends read the same beta feature: the `studio ui` server resolves it from app.json through a shared helper rather than staying on Jetpack silently. This is a re-application of #4430 onto #4582; the original branch could not be rebased, so its commits do not appear here. Co-authored-by: Gergely Csécsey <gcsecsey@gmail.com> Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Reprint pull used to ignore what the user picked and fetch the whole site. The selection was only ever reduced to Jetpack backup node ids, which Reprint has no use for, but the tree it came from also carries the wp-content path Reprint does select by. The dialog now reduces the tree a second way, to wp-content-relative paths and a database flag, and both forms travel together so the engine can be resolved where the pull runs. Picking a single plugin now pulls only that plugin. The new reduction mirrors the CLI picker's, and both are tested against the same cases so they cannot drift apart. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
A Jetpack pull can warn before downloading, because it sizes up the one backup archive it is about to fetch. Reprint streams the site in pieces, so there is no total to read up front and the question can only be answered afterwards. Studio now measures wp-content once the pull finishes and says so if the site is over the limit. The tally is of uncompressed files while the limit applies to a gzipped upload, so it over-reports and the wording says the push "may" fail. That bias is deliberate: a false alarm is cheap, and staying quiet would let the user hit the ceiling at push time instead. Both front ends answer the question the same way, through one function that is meant to be deleted once push moves to Reprint and the ceiling goes away. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Contributor
|
@fredrikekelund I tested this and it kept failing. There is some path mismatch issue. Let me know if I need to something differently. |
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.
Related issues
How AI was used in this PR
I used Claude for both planning and implementation.
Proposed Changes
This PR includes all changes from #4430, with resolved merge conflicts, plus logic to display a "Your site's backup exceeds 5 GB" warning after the pull, if applicable.
That warning is needed because even when Reprint pulls are launched, pushes will still be powered by Jetpack Backups for a while longer, and there's a 5 GB upload limit for those that continues to apply.
Testing Instructions
TBD
Pre-merge Checklist