@geekity/plugin-wordpress 0.0.0-stage → 0.1.1

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.
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Andrew Shell
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md CHANGED
@@ -1,3 +1,152 @@
1
- # Temporary Holding Version
1
+ # @geekity/plugin-wordpress
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ A [Geekity CMS](../cms/README.md) plugin for a site that moved from the
4
+ WordPress ActivityPub plugin. It answers the old plugin's inbox and collection
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.
8
+
9
+ A site born on the CMS never needs it. Stored actor ids, post object ids,
10
+ WebFinger aliases and the feed and archive layout are the site's own and are in
11
+ `@geekity/cms` (decision-14).
12
+
13
+ ## Installing it
14
+
15
+ It works with every 0.x release of `@geekity/cms` from the one it was built
16
+ against, which its peer dependency names.
17
+
18
+ Add the package to the site and to `plugins` in its config:
19
+
20
+ ```sh
21
+ pnpm add @geekity/plugin-wordpress
22
+ ```
23
+
24
+ ```ts
25
+ import { defineConfig } from '@geekity/cms';
26
+ import wordpress from '@geekity/plugin-wordpress';
27
+
28
+ export default defineConfig({
29
+ plugins: [wordpress],
30
+ });
31
+ ```
32
+
33
+ On Docker, add the package to the plugins folder instead, then press Reload
34
+ under `/admin/plugins`:
35
+
36
+ ```sh
37
+ docker compose exec geekity geekity plugin add @geekity/plugin-wordpress
38
+ ```
39
+
40
+ `geekity import wordpress-actor` works as soon as the plugin is installed. The
41
+ old paths are served only while the plugin is enabled under `/admin/plugins`,
42
+ and disabling it takes them away on the next request, with nothing restarted.
43
+
44
+ The plugin keeps two files in `data/plugins/@geekity/plugin-wordpress/`:
45
+
46
+ | File | What it holds |
47
+ | --------------- | --------------------------------------------------------------------------------------------------------------------------- |
48
+ | `actors.json` | Each username and the number WordPress gave that person's actor, which the old paths are built from. Written by the import. |
49
+ | `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. |
50
+
51
+ ## The old paths
52
+
53
+ A follower's server still holds the WordPress plugin's endpoints, such as
54
+ `/wp-json/activitypub/1.0/actors/2/inbox`, the shared
55
+ `/wp-json/activitypub/1.0/inbox`, and the collections beside them. Those are
56
+ cache rather than identity: a follower's server replaces them the next time it
57
+ refetches the actor. So the site carries them for a while, and is meant to
58
+ stop.
59
+
60
+ While the plugin is enabled, those paths are real inbox routes,
61
+ signature-verified exactly as the site's own are, so an unsigned or badly
62
+ signed delivery is refused, plus GET routes for the actor, its outbox, its
63
+ followers and its following. What a peer reads back is always the canonical
64
+ document: the same `id`, the same key, and the _canonical_ inbox and
65
+ collections, so a follower refetching the actor at the old URL is the follower
66
+ that learns the new endpoints. A user with no number in `actors.json` is not
67
+ reachable through any of it.
68
+
69
+ Every one of those paths records the instant it was last asked for, and the
70
+ plugin's screen under Plugins, `/admin/plugins/@geekity/plugin-wordpress`,
71
+ lists each path with that instant, or _Never_. Once every follower's server has
72
+ refetched the actor, nothing asks any more, and the plugin can be disabled.
73
+
74
+ ## Bringing a person across
75
+
76
+ `geekity import wordpress-actor` brings one person across. It writes the three
77
+ things the site cannot mint for itself, the RSA key pair the followers have
78
+ cached, the actor id they key the account by, and the followers, and it is a
79
+ command rather than a screen because a stored actor id is identity for the life
80
+ of the account.
81
+
82
+ ```sh
83
+ geekity import wordpress-actor ada \
84
+ --actor-id 'https://example.com/?author=2' \
85
+ --wordpress-id 2 \
86
+ --keypair ada.keypair.json
87
+ ```
88
+
89
+ It writes `data/keys/ada.rsassa-pkcs1-v1_5.jwk` (and mints the Ed25519 pair
90
+ beside it, which WordPress never had), puts `actorId` on the user in
91
+ `data/users.json` and the number in the plugin's `actors.json`, and fetches the
92
+ WordPress plugin's public followers collection into
93
+ `content/_data/federation/ada/followers.json`, dereferencing each follower for
94
+ its inbox, shared inbox, handle, name, avatar and profile URL.
95
+
96
+ | Option | What it is |
97
+ | --------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------- |
98
+ | `--actor-id <url>` | The id WordPress published, query string and all. The one thing the import cannot work out for itself. |
99
+ | `--wordpress-id <n>` | The WordPress user id, which is the number in the plugin's paths. |
100
+ | `--keypair <file>` | The JSON the plugin's option holds: `{"private_key": …, "public_key": …}`. |
101
+ | `--private-key <file>`, `--public-key <file>` | The two PEMs instead, for a site still on the legacy user meta. The public key is only checked against the private half; it is derived, never stored. |
102
+ | `--followers <url\|file\|none>` | Where the followers come from. Left off, the plugin's own collection on the actor id's origin. |
103
+ | `--force` | Import over a key pair the user already has. |
104
+
105
+ **Exporting the key pair with wp-cli.** The WordPress plugin keeps a user's
106
+ pair in an option named after their login:
107
+
108
+ ```sh
109
+ # On the WordPress server. The login, not the user id, is what names the option.
110
+ wp option get activitypub_keypair_for_ada --format=json > ada.keypair.json
111
+ ```
112
+
113
+ A site old enough to still be on the plugin's legacy storage has the pair in
114
+ user meta instead, one PEM per key:
115
+
116
+ ```sh
117
+ wp user meta get 2 magic_sig_private_key > ada.private.pem
118
+ wp user meta get 2 magic_sig_public_key > ada.public.pem
119
+ ```
120
+
121
+ Either export is accepted, and either PEM encoding, `-----BEGIN PRIVATE
122
+ KEY-----` (PKCS#8) or `-----BEGIN RSA PRIVATE KEY-----` (PKCS#1). Treat the
123
+ files the way you would treat a password: the private key is the account.
124
+
125
+ The followers can be saved before the old site goes away, and the saved file
126
+ passed to `--followers`:
127
+
128
+ ```sh
129
+ curl -H 'Accept: application/activity+json' \
130
+ 'https://example.com/wp-json/activitypub/1.0/actors/2/followers?page=1' > followers.json
131
+ ```
132
+
133
+ **Running it twice changes nothing**, and says so, which is what makes it safe
134
+ to run again after an instance that was down comes back: a follower already in
135
+ the file keeps its place and its follow time. A follower whose server will not
136
+ answer is listed and skipped rather than failing the import. A user who already
137
+ has a _different_ key pair is refused: importing over one would change that
138
+ person's identity, and every follower has cached the public half of the key
139
+ that is there. `--force` is the way past that, and is only right when you are
140
+ certain the pair being imported is the one the followers hold. An actor id or a
141
+ WordPress number another user already carries is refused too, before anything
142
+ is written.
143
+
144
+ The cutover, end to end, is in the `@geekity/cms` README under _Moving a site
145
+ from the WordPress ActivityPub plugin_.
146
+
147
+ ## The bundle
148
+
149
+ The package's build also writes `dist/bundle/index.js`, one module with its
150
+ dependencies inlined and the plugin as its default export. It is what a
151
+ plugins folder holds on a site that runs the Docker image, which has no
152
+ `node_modules` of its own (decision-33).