@geekity/plugin-wordpress 0.1.0 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (45) hide show
  1. package/README.md +174 -9
  2. package/dist/bundle/index.js +24337 -2098
  3. package/dist/bundle/plugin.json +2 -2
  4. package/dist/comments-import.d.ts +3 -0
  5. package/dist/comments-import.d.ts.map +1 -0
  6. package/dist/comments-import.js +155 -0
  7. package/dist/comments-import.js.map +1 -0
  8. package/dist/content-command.d.ts +3 -0
  9. package/dist/content-command.d.ts.map +1 -0
  10. package/dist/content-command.js +100 -0
  11. package/dist/content-command.js.map +1 -0
  12. package/dist/content-import.d.ts +53 -0
  13. package/dist/content-import.d.ts.map +1 -0
  14. package/dist/content-import.js +251 -0
  15. package/dist/content-import.js.map +1 -0
  16. package/dist/index.d.ts.map +1 -1
  17. package/dist/index.js +6 -1
  18. package/dist/index.js.map +1 -1
  19. package/dist/media-import.d.ts +3 -0
  20. package/dist/media-import.d.ts.map +1 -0
  21. package/dist/media-import.js +89 -0
  22. package/dist/media-import.js.map +1 -0
  23. package/dist/media-redirect.d.ts +4 -0
  24. package/dist/media-redirect.d.ts.map +1 -0
  25. package/dist/media-redirect.js +28 -0
  26. package/dist/media-redirect.js.map +1 -0
  27. package/dist/media.d.ts +14 -0
  28. package/dist/media.d.ts.map +1 -0
  29. package/dist/media.js +102 -0
  30. package/dist/media.js.map +1 -0
  31. package/dist/posts-import.d.ts +34 -0
  32. package/dist/posts-import.d.ts.map +1 -0
  33. package/dist/posts-import.js +301 -0
  34. package/dist/posts-import.js.map +1 -0
  35. package/dist/version.d.ts +1 -1
  36. package/dist/version.js +1 -1
  37. package/dist/wordpress-html.d.ts +7 -0
  38. package/dist/wordpress-html.d.ts.map +1 -0
  39. package/dist/wordpress-html.js +360 -0
  40. package/dist/wordpress-html.js.map +1 -0
  41. package/dist/wxr.d.ts +80 -0
  42. package/dist/wxr.d.ts.map +1 -0
  43. package/dist/wxr.js +212 -0
  44. package/dist/wxr.js.map +1 -0
  45. package/package.json +11 -4
package/README.md CHANGED
@@ -3,8 +3,10 @@
3
3
  A [Geekity CMS](../cms/README.md) plugin for a site that moved from the
4
4
  WordPress ActivityPub plugin. It answers the old plugin's inbox and collection
5
5
  paths until every follower's server has refetched the actor, records when each
6
- path was last asked for, and adds `geekity import wordpress-actor`, which
7
- brings each person's key pair, actor id and followers across.
6
+ path was last asked for, and adds two imports: `geekity import wordpress`,
7
+ which brings the content of a WordPress export across, and
8
+ `geekity import wordpress-actor`, which brings each person's key pair, actor id
9
+ and followers across.
8
10
 
9
11
  A site born on the CMS never needs it. Stored actor ids, post object ids,
10
12
  WebFinger aliases and the feed and archive layout are the site's own and are in
@@ -12,6 +14,9 @@ WebFinger aliases and the feed and archive layout are the site's own and are in
12
14
 
13
15
  ## Installing it
14
16
 
17
+ It works with every 0.x release of `@geekity/cms` from the one it was built
18
+ against, which its peer dependency names.
19
+
15
20
  Add the package to the site and to `plugins` in its config:
16
21
 
17
22
  ```sh
@@ -34,16 +39,18 @@ under `/admin/plugins`:
34
39
  docker compose exec geekity geekity plugin add @geekity/plugin-wordpress
35
40
  ```
36
41
 
37
- `geekity import wordpress-actor` works as soon as the plugin is installed. The
38
- old paths are served only while the plugin is enabled under `/admin/plugins`,
39
- and disabling it takes them away on the next request, with nothing restarted.
42
+ Both imports work as soon as the plugin is installed. The
43
+ old paths, and the redirects from WordPress media URLs, are served only while
44
+ the plugin is enabled under `/admin/plugins`, and disabling it takes them away
45
+ on the next request, with nothing restarted.
40
46
 
41
47
  The plugin keeps two files in `data/plugins/@geekity/plugin-wordpress/`:
42
48
 
43
- | File | What it holds |
44
- | --------------- | --------------------------------------------------------------------------------------------------------------------------- |
45
- | `actors.json` | Each username and the number WordPress gave that person's actor, which the old paths are built from. Written by the import. |
46
- | `requests.json` | When each old path was last asked for, per user, and when the shared inbox was. Losing it resets what the screen says. |
49
+ | File | What it holds |
50
+ | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
51
+ | `actors.json` | Each username and the number WordPress gave that person's actor, which the old paths are built from. Written by the import. |
52
+ | `requests.json` | When each old path was last asked for, per user, and when the shared inbox was. Losing it resets what the screen says. |
53
+ | `import.json` | A hash of every file `geekity import wordpress` wrote, by path, and each `site.json`, `media.json` and redirect key it set. Losing it makes every imported file a clash on the next run. |
47
54
 
48
55
  ## The old paths
49
56
 
@@ -68,6 +75,164 @@ plugin's screen under Plugins, `/admin/plugins/@geekity/plugin-wordpress`,
68
75
  lists each path with that instant, or _Never_. Once every follower's server has
69
76
  refetched the actor, nothing asks any more, and the plugin can be disabled.
70
77
 
78
+ ## Bringing the content across
79
+
80
+ `geekity import wordpress` reads the WXR file WordPress writes under Tools >
81
+ Export (or `wp export`) and writes what it holds into the content directory:
82
+
83
+ ```sh
84
+ geekity import wordpress example.WordPress.2026-10-08.xml \
85
+ --uploads ./wp-content/uploads \
86
+ --origins https://staging.example.com
87
+ ```
88
+
89
+ `--uploads` is a copy of the site's `wp-content/uploads` (an export carries
90
+ no files), and `--origins` names any other host the posts wrote media URLs
91
+ on, such as the staging host the site was built on, comma-separated. Both are
92
+ optional; see [Media](#media).
93
+
94
+ A file that is not a WordPress export is refused before anything is written,
95
+ with each problem named, and the command exits 1.
96
+
97
+ The command prints the totals, then one tab-separated row per item: its
98
+ outcome, post type, WordPress id, title, the file it went to, and why.
99
+
100
+ | Outcome | What it means |
101
+ | ----------- | -------------------------------------------------------------------------------------------------------- |
102
+ | `written` | The file is new, was changed on WordPress since the last run, or was removed here and is written again. |
103
+ | `unchanged` | The file already holds what WordPress has. |
104
+ | `kept` | The file was edited on this site and WordPress has not changed it since. The edit stays. |
105
+ | `conflict` | The file was edited on this site and changed on WordPress too. The edit stays; the row gives both dates. |
106
+ | `clash` | A file the import never wrote is at the path. It is left alone. |
107
+ | `skipped` | The item has no Geekity counterpart, such as a revision, a menu or the ActivityPub plugin's records. |
108
+ | `warned` | The item was imported, with something the operator should check. |
109
+
110
+ **Run it as often as you like.** Two runs over one export leave the content
111
+ directory byte for byte as one run did, and a run over a newer export picks up
112
+ what was added or edited on WordPress since. The import never writes over a file
113
+ it did not write, and never deletes one, so a site can layer its own fixes on
114
+ top of an import. To take WordPress's side of a conflict, remove the file and
115
+ run the import again.
116
+
117
+ ### Posts and pages
118
+
119
+ Each post becomes `posts/YYYY-MM-DD-slug.md`, dated by its local publish date,
120
+ and each page becomes `pages/slug.md`. The front matter keeps what readers,
121
+ feed readers and followers already know of it:
122
+
123
+ | Key | Where it comes from |
124
+ | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
125
+ | `permalink` | The URL WordPress served, from the export's link. A draft has none yet, so it gets the one the published posts' structure gives it; a page gets its parents' slugs. |
126
+ | `redirect_from` | The URLs the post had before WordPress renamed or redated it: its permalink with each `_wp_old_slug` in place of its slug, and each `_wp_old_date` in place of its date. |
127
+ | `date` | `post_date_gmt`. A draft WordPress never dated has none. |
128
+ | `updated` | `post_modified_gmt`, when it differs from the date. |
129
+ | `tags` | The post's tags, by name. |
130
+ | `categories` | The post's categories, by name. |
131
+ | `draft` | `true` for a draft, a pending post, and a private or password-protected one, which the report names. A scheduled post keeps its date and publishes then. |
132
+ | `author` | The WordPress login, when the site has a user by that name. A post by any other login has no author, and the report names it. |
133
+ | `in-reply-to` | The URL an ActivityPub plugin reply block answers. |
134
+ | `activitypub` | On a published post, `id` is `https://example.com/?p=ID`, the object id the ActivityPub plugin served. A post the plugin federated (`activitypub_status` is `federated`) also has `published`. |
135
+ | `guid` | The WordPress guid, when it is not the post's `?p=` address, so feed readers see no old post as new. |
136
+ | `migrated` | `true` on everything but a scheduled post, so its arrival here, or a draft's publication later, sends nothing to anyone. A scheduled post publishes here as news, as it would have on WordPress. |
137
+
138
+ A post in the status format has no title, so it reads as a note. The page
139
+ WordPress served at the home URL becomes `homepage` in `_data/site.json`. The
140
+ import sets that key only while the site has not set it, or still holds what
141
+ the import last set.
142
+
143
+ The body becomes Markdown. Block comments go, a classic post's line breaks
144
+ become paragraphs as WordPress showed them, and a YouTube or Vimeo iframe or
145
+ embed becomes the video's URL on a line of its own, which the site plays.
146
+ What Markdown cannot say stays HTML: other iframes, a figure with a caption, a
147
+ table with spans, and an element carrying a `style` or microformats class.
148
+ `<!--more-->` stays where it was. Media URLs point at the files under
149
+ `/uploads/`, as [Media](#media) describes.
150
+
151
+ ### Media
152
+
153
+ Each attachment's original is copied from `--uploads` into `content/uploads/`
154
+ at the path WordPress kept it under, such as `uploads/2024/03/photo.png`.
155
+ WordPress's size variants (`photo-1024x575.png`, `photo-scaled.png`) are not
156
+ copied: the site makes its own. A file a post or page shows that is no
157
+ attachment is copied too, for that post.
158
+
159
+ Every media URL in a body, in Markdown and in the HTML it keeps, on the
160
+ export's own origin, on an `--origins` host, or root-relative, becomes a
161
+ root-relative `/uploads/` URL of the original. One pointing at a size variant
162
+ points at its original. An upload URL on any other host is left alone.
163
+
164
+ Each file is held to the rules an upload through the editor is: the site's
165
+ allowed types and size limits, and location and camera details removed. A
166
+ file the site would refuse is not copied. The report names it, and names
167
+ each file missing from `--uploads`. Without `--uploads` nothing is copied and
168
+ the report names each attachment.
169
+
170
+ An attachment's alt text (`_wp_attachment_image_alt`) goes to
171
+ `_data/media.json` under its upload path, which is where the editor offers it
172
+ from. The import owns each key it set there, never the file.
173
+
174
+ While the plugin is enabled, a request for `/wp-content/uploads/<path>`
175
+ answers 301 at `/uploads/<path>`, so links from elsewhere still land on the
176
+ file. A size variant the import did not copy lands on its original, as
177
+ `import.json` records it, and the query string is kept. No `redirects.json`
178
+ entry is written per file. Keep the plugin enabled for as long as links to the
179
+ old media URLs matter.
180
+
181
+ ### Comments and reactions
182
+
183
+ Every comment WordPress kept on a post or page the import writes goes into the
184
+ post's comment file, `_data/comments/<slug>.json`, the file the site's own
185
+ comments go to. That covers form comments, and the likes, reposts, replies,
186
+ mentions, bookmarks and pingbacks the ActivityPub and Webmention plugins stored
187
+ as comments. Each lands under the post as one the site received itself would:
188
+
189
+ | WordPress | Here |
190
+ | --------------------------------------------------------- | -------------------------------------------------------------- |
191
+ | `comment`, `like`, `repost` with `protocol` `activitypub` | A fediverse reply, like or boost, linked to the remote post. |
192
+ | `comment`, `like`, `repost` with `protocol` `webmention` | A webmention reply, like or repost, linked to its source page. |
193
+ | `mention`, `bookmark`, `webmention` | A mention, linked to the page that linked here. |
194
+ | `pingback`, `trackback` | A mention, linked to the page that pinged. |
195
+ | `comment` with no protocol | A comment left on the page. |
196
+
197
+ A reply keeps the comment it answers. An approved comment is approved here,
198
+ and one WordPress held for moderation waits here too. Spam and trash are left
199
+ out and named in the report. Each comment keeps the id WordPress's comments
200
+ feed gave it, `https://example.com/?p=ID#comment-N`, so a feed reader sees
201
+ nothing it has not seen. A commenter's email goes only to `data/comments/`.
202
+ Nothing is announced: no moderator is emailed and nothing is delivered.
203
+
204
+ The import merges by comment id. A rerun adds the comments WordPress received
205
+ since and picks up one edited there. It leaves alone a comment this site
206
+ received itself, and an imported one a moderator approved, filed as spam,
207
+ edited or deleted.
208
+
209
+ ### Redirects
210
+
211
+ Links from elsewhere use URL forms WordPress answered and the site has no
212
+ document at. The import declares a redirect for each in
213
+ `_data/redirects/wordpress.json`, a file of its own, which the site reads beside
214
+ `_data/redirects.json` with no plugin enabled:
215
+
216
+ | URL | Answers 301 at |
217
+ | ------------------------------------------- | --------------------------- |
218
+ | `/?p=ID`, for every published post and page | Its permalink. |
219
+ | `/?page_id=ID`, for every published page | Its permalink. |
220
+ | An attachment's page, such as `/photo-jpg/` | Its file under `/uploads/`. |
221
+ | `/?attachment_id=ID` and `/?p=ID`, likewise | Its file under `/uploads/`. |
222
+
223
+ A draft, a pending, private, password-protected or scheduled post or page gets
224
+ no redirect, so its `?p=` answers as an id WordPress never had. An attachment
225
+ whose file was not copied gets none either, until a run copies it. A post's
226
+ old slugs and dates go into its own `redirect_from`, as
227
+ [Posts and pages](#posts-and-pages) says. A published post's `?p=` is also its
228
+ `activitypub.id`, which the site answers first, at the same permalink.
229
+
230
+ The import owns each key it set in `wordpress.json`, never the file, and never
231
+ touches `_data/redirects.json`. A key the site added there, or changed, is left
232
+ alone on every rerun. Where both files declare one URL, `redirects.json` wins.
233
+ A link to a comment, `#comment-N`, reaches the post: the fragment never
234
+ reaches the server, so the browser scrolls to it if the comment is there.
235
+
71
236
  ## Bringing a person across
72
237
 
73
238
  `geekity import wordpress-actor` brings one person across. It writes the three