fix(import): chunked, resumable WordPress plugin import (fixes Worker resource limits)#1849
Conversation
Scope checkThis PR changes 3,301 lines across 21 files. Large PRs are harder to review and more likely to be closed without review. If this scope is intentional, no action needed. A maintainer will review it. If not, please consider splitting this into smaller PRs. See CONTRIBUTING.md for contribution guidelines. |
🦋 Changeset detectedLatest commit: ad7b420 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 |
A single /execute invocation importing a whole site exceeds Cloudflare Worker resource limits (CPU, subrequests) on sites with 500+ posts (emdash-cms#475). The admin now drives the import as a loop of bounded requests: one WP content page (50 posts) per call, then paginated comments, then a small finalize step (menus + site identity). Media uploads in batches of 25 instead of one giant streaming request. Cross-chunk state — the WP-ID -> EmDash-ID map, translation groups, comment threading roots — is accumulated by the client and sent back with each request, so the server stays stateless. An aborted import re-runs safely: skipExisting skips existing content while rebuilding the ID maps the later phases need. Requests without a phase keep the single-shot behavior, so older clients are unaffected. The wizard shows real progress (imported/total with a progress bar) instead of an indefinite spinner.
1191c44 to
1782ba9
Compare
There was a problem hiding this comment.
The PR’s approach is sound: moving the WordPress plugin import to client-driven, stateless chunks directly addresses issue #475 and fits EmDash’s Worker/serverless model. The route split keeps the legacy single-shot path intact, and the client carries the cross-chunk ID maps, translation groups, and comment roots as designed. Supporting changes in importContent, importCommentsFromPlugin, and loadTaxonomyPlanFromDb are covered by targeted tests.
I reviewed the diff statically, reading the new route handler, schema, admin API client, the wizard component, and supporting source/taxonomy/comments code. No SQL-injection, authorization, or data-loss regressions stood out, and the content lookups remain locale-aware. The items in findings are all fixable before merge: dead UI toggles, unlocalized or hard-coded English strings, and dark: Tailwind prefixes that violate the repo’s Kumo-based styling rules.
Findings
-
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:546The new
importMenus,importSiteTitle,importLogo, andimportSeotoggles introduced around line 161 are rendered inReviewStep, but thisconfigobject never includes them, and the server-sideexecute.tsnever reads them. As a result the toggles are non-functional: menus, site settings, and SEO fields are imported regardless of the user’s choices. Either wire them throughImportConfig/the Zod schema and conditionally skip those steps on the server, or remove the toggles until they are implemented. -
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:1040These placeholders are hard-coded English strings shown to users. AGENTS.md requires every user-facing string to go through Lingui. The same applies to the URL placeholder on line 1085.
placeholder={t`em1.…`} -
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:1476Hard-coded English placeholders in the manual plugin-auth form. The application-password placeholder on line 1490 has the same problem.
placeholder={t`admin`} -
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:208Hard-coded error messages surfaced to the UI.
tshould wrap these so they are extracted for localization. Line 494 has the same issue ("OAuth authorization requires HTTPS. Please use manual credentials.").setImportError(t`WordPress authorization was rejected`); -
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:1140-1162The feature names and notes in
FEATURE_COMPARISONare rendered as user-visible text (item.featureandnote) but are hard-coded English constants. They should be defined as Linguimsgdescriptors and resolved witht()inside the component so they can be translated. -
[needs fixing]
packages/admin/src/components/WordPressImport.tsx:1074The component repeatedly uses
dark:Tailwind prefixes (e.g., here, line 1196, 1213, 1435, and elsewhere). AGENTS.md forbidsdark:prefixes in the admin UI in favor of Kumo semantic tokens (bg-kumo-tint,text-kumo-subtle, etc.) that use CSSlight-dark()internally.<div className="p-3 rounded-full bg-kumo-tint">
@emdash-cms/admin
@emdash-cms/auth
@emdash-cms/auth-atproto
@emdash-cms/blocks
@emdash-cms/cloudflare
@emdash-cms/contentful-to-portable-text
emdash
create-emdash
@emdash-cms/gutenberg-to-portable-text
@emdash-cms/plugin-cli
@emdash-cms/plugin-types
@emdash-cms/registry-client
@emdash-cms/registry-lexicons
@emdash-cms/sandbox-workerd
@emdash-cms/x402
@emdash-cms/plugin-ai-moderation
@emdash-cms/plugin-atproto
@emdash-cms/plugin-audit-log
@emdash-cms/plugin-color
@emdash-cms/plugin-embeds
@emdash-cms/plugin-field-kit
@emdash-cms/plugin-forms
@emdash-cms/plugin-webhook-notifier
commit: |
…ngs, drop dark: prefixes Addresses bot review on emdash-cms#1849: - importMenus/importSiteTitle/importLogo/importSeo now flow through ImportConfig and are honored by both the legacy single-shot path and the chunked finalize/content phases (absent = enabled, so older admins keep the previous behavior) - menus toggle only renders for the plugin source (WXR never imports menus) - hard-coded placeholders, error messages, and FEATURE_COMPARISON entries go through Lingui (msg descriptors resolved with t()) - dark: Tailwind prefixes replaced with Kumo semantic tokens
|
Thanks for the review — all findings addressed in ad7b420:
Lint, typecheck, and the import test suites pass locally. |
What does this PR do?
Fixes #475 for the plugin import path. A single
/executeinvocation that imports a whole site — taxonomy setup, every content page, menus, all comments, site settings — exceeds Cloudflare Worker resource limits (CPU time, subrequests) on sites with a few hundred posts, and there is no way to resume after the failure.This PR makes the import chunked and resumable, as announced in discussion #1831 (comment):
/executegains optionalphase+cursorparams. The admin wizard drives the import as a loop of bounded requests: one WP content page (50 posts) per call, then paginated comments (500/page), then a small finalize step (menus + site identity). Each invocation stays far below CPU and subrequest limits on both free and paid plans.skipExistingskips already-imported content while rebuilding the ID maps, so the import fast-forwards to where it stopped. Comments and media are idempotent the same way (timestamp/body match, content-hash dedupe).phasebehaves exactly as before, so older admin clients keep working.Later content chunks reload the taxonomy lookup maps with two SELECTs (
loadTaxonomyPlanFromDb) instead of re-walking every term through the creation path; term/def creation runs once in the first chunk.Update: #1830 has merged — this branch is now rebased onto main and contains only the chunked-import commit.
Integration tests cover the state that must survive chunk boundaries: translation-group linking across chunks, ID-map rebuild on a resumed run, taxonomy plan reload, and comment threading across pages.
Type of change
Checklist
AI-generated code disclosure