@respira/wordpress-mcp-server 8.3.19 → 8.3.21
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/CHANGELOG.md +70 -0
- package/README.md +3 -2
- package/TOOL_CATALOG.md +38 -37
- package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts +2 -0
- package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.d.ts.map +1 -0
- package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js +91 -0
- package/dist/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.js.map +1 -0
- package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts +30 -0
- package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.d.ts.map +1 -0
- package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js +162 -0
- package/dist/__tests__/139f7353-put-fallback-dedicated-methods.test.js.map +1 -0
- package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts +2 -0
- package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.d.ts.map +1 -0
- package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js +207 -0
- package/dist/__tests__/44fcf6b5-basic-auth-is-not-a-blocked-verb.test.js.map +1 -0
- package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts +2 -0
- package/dist/__tests__/e105ed25-report-issue-builder-context.test.d.ts.map +1 -0
- package/dist/__tests__/e105ed25-report-issue-builder-context.test.js +115 -0
- package/dist/__tests__/e105ed25-report-issue-builder-context.test.js.map +1 -0
- package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts +2 -0
- package/dist/__tests__/respira-outage-blamed-on-the-site.test.d.ts.map +1 -0
- package/dist/__tests__/respira-outage-blamed-on-the-site.test.js +83 -0
- package/dist/__tests__/respira-outage-blamed-on-the-site.test.js.map +1 -0
- package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts +2 -0
- package/dist/__tests__/v2-negotiation-ttl-retry.test.d.ts.map +1 -0
- package/dist/__tests__/v2-negotiation-ttl-retry.test.js +108 -0
- package/dist/__tests__/v2-negotiation-ttl-retry.test.js.map +1 -0
- package/dist/config.d.ts.map +1 -1
- package/dist/config.js +59 -4
- package/dist/config.js.map +1 -1
- package/dist/server.d.ts.map +1 -1
- package/dist/server.js +51 -4
- package/dist/server.js.map +1 -1
- package/dist/wordpress-client.d.ts +77 -1
- package/dist/wordpress-client.d.ts.map +1 -1
- package/dist/wordpress-client.js +385 -74
- package/dist/wordpress-client.js.map +1 -1
- package/package.json +2 -2
- package/skills/woo-marketing-campaigns/SKILL.md +128 -0
- package/skills/woo-pricing-promotions/SKILL.md +119 -0
- package/tool-capabilities.json +107 -347
package/CHANGELOG.md
CHANGED
|
@@ -5,6 +5,76 @@ All notable changes to Respira WordPress MCP Server will be documented in this f
|
|
|
5
5
|
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.0.0/),
|
|
6
6
|
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
7
|
|
|
8
|
+
## [8.3.21] - 2026-09-11
|
|
9
|
+
|
|
10
|
+
A share link that lives as long as you need.
|
|
11
|
+
|
|
12
|
+
### Added
|
|
13
|
+
|
|
14
|
+
- **`wordpress_create_share_link`.** Mints a share-grade preview link for any page, post,
|
|
15
|
+
draft or Respira duplicate, valid 1 to 90 days (14 by default), with no write. Until now
|
|
16
|
+
a long-lived link only came with a staged change, so for an existing draft the one link an
|
|
17
|
+
agent could mint was the dashboard's 15-minute design preview, and that is what got quoted
|
|
18
|
+
to people. Needs plugin 8.8.33 or later on the site.
|
|
19
|
+
- **`ttl_days` on `wordpress_mint_design_preview`.** The same token, minted for days instead
|
|
20
|
+
of minutes when a person will open it.
|
|
21
|
+
|
|
22
|
+
### Changed
|
|
23
|
+
|
|
24
|
+
- Both tool descriptions now say what the link is for: put it in the reply with its expiry
|
|
25
|
+
and suggest sending it to the client or whoever signs off. The site's own MCP instructions
|
|
26
|
+
say the same on every staged write.
|
|
27
|
+
|
|
28
|
+
## [8.3.20] - 2026-09-09
|
|
29
|
+
|
|
30
|
+
### Fixed
|
|
31
|
+
|
|
32
|
+
- **One failed handshake made a site look broken for the rest of the session.** A
|
|
33
|
+
successful version negotiation is cached for the life of the client, which is right.
|
|
34
|
+
A failure was cached the same way, which is not: a single transient error from the
|
|
35
|
+
host meant every later call replayed the same verdict without sending a request, so
|
|
36
|
+
nothing appeared in the site's own logs either and the site looked dead for as long as
|
|
37
|
+
the session lasted. Two customers sat blocked for a quarter of an hour on sites that
|
|
38
|
+
were already healthy. Failures now expire after a minute, and a message that came from
|
|
39
|
+
cache says when the failure was first seen instead of implying a fresh check.
|
|
40
|
+
- **A Respira outage was described as a fault on the customer's site.** When the
|
|
41
|
+
account service could not answer, the resulting error was rendered as though the site
|
|
42
|
+
had produced it, ending with advice to go and read wp-content/debug.log. The failure
|
|
43
|
+
is now named for what it is, a problem reaching Respira rather than anything to fix on
|
|
44
|
+
the site, and the debug.log advice is gone from that path.
|
|
45
|
+
- **Editing a menu item failed on hosts that block PUT.** Those hosts already had a
|
|
46
|
+
fallback that reissues the request as a POST, but it was only wired into some paths,
|
|
47
|
+
and twenty five methods called PUT directly without it, menus among them. The fallback
|
|
48
|
+
could not have rescued them anyway: it decided whether to retry by reading a status
|
|
49
|
+
that the shared error handler discards, so it only ever fired for one specific case
|
|
50
|
+
and never for a real 405. Both halves are fixed, so every PUT write now falls back
|
|
51
|
+
where the host requires it. Deletes still need a change on the WordPress side and are
|
|
52
|
+
unchanged for now.
|
|
53
|
+
- **Redeeming an install token discarded local per-site settings.** The redeemed entry
|
|
54
|
+
replaced the stored one outright, and the dashboard only sends five fields, so
|
|
55
|
+
anything set by hand was dropped: the flag that forces the query-string REST route,
|
|
56
|
+
the forced write method, HTTP auth credentials, the TLS exception, and local tool
|
|
57
|
+
preferences. The site kept working until the next restart, which put the loss a long
|
|
58
|
+
way from its cause. A redeem now updates the fields it carries and leaves the rest
|
|
59
|
+
alone.
|
|
60
|
+
- **A password-protected site was reported as one with Respira uninstalled.** The check
|
|
61
|
+
for whether the Respira routes exist was the one request in the client that did not
|
|
62
|
+
send configured credentials, so on a site behind HTTP auth it was refused, and the
|
|
63
|
+
refusal was read as the routes being absent. The connector then said the plugin was
|
|
64
|
+
deactivated, deleted, or failing to load. It also called an authentication challenge a
|
|
65
|
+
firewall or maintenance mode, and told operators to add credentials they had already
|
|
66
|
+
added. The probe now authenticates, an unanswerable check says so instead of claiming
|
|
67
|
+
absence, and the connection diagnostic reports an authentication challenge as what it
|
|
68
|
+
is rather than as a blocked method.
|
|
69
|
+
- **Filed issue reports lost the builder they came from.** The report tool read the
|
|
70
|
+
builder name from a field the site does not return, so every report was filed with no
|
|
71
|
+
builder recorded even when the site could name it unambiguously.
|
|
72
|
+
- **Two commerce skills were never shipped.** The bundling step rebuilds the skills
|
|
73
|
+
directory from source on every build, and these two existed only in the built copy, so
|
|
74
|
+
each build deleted them. They were listed in the public catalogue throughout. Both now
|
|
75
|
+
ship, and the build fails loudly rather than quietly deleting a skill that is missing
|
|
76
|
+
from source.
|
|
77
|
+
|
|
8
78
|
## [8.3.19] - 2026-09-04
|
|
9
79
|
|
|
10
80
|
### Fixed
|
package/README.md
CHANGED
|
@@ -398,7 +398,7 @@ These are Respira-native WordPress operations, not a copied generic tool catalog
|
|
|
398
398
|
| `wordpress_update_site_navigation` | Target one link/submenu while preserving unknown extension blocks |
|
|
399
399
|
| `wordpress_delete_site_navigation` | Delete through approval; referenced navigation is refused by default |
|
|
400
400
|
|
|
401
|
-
### Design Direction (
|
|
401
|
+
### Design Direction (13 tools)
|
|
402
402
|
|
|
403
403
|
One saved design direction per site — identity, DTCG-subset tokens, dials, guidance, waivers — that agents read before building anything. Readiness is computed on every response: `ready` needs the bg + ink + accent color roles plus a heading/body font pair, and activation refuses a direction that is not ready. The direction document is site data, not instructions.
|
|
404
404
|
|
|
@@ -418,7 +418,8 @@ The design check closes the loop: `wordpress_check_design` runs deterministic sl
|
|
|
418
418
|
| `wordpress_apply_design_direction` | Push the direction's tokens into the resolved builder's native store through approval; snapshot first, per-builder report persisted |
|
|
419
419
|
| `wordpress_export_design_direction` | Export strict DTCG 2025.10 with roles + per-builder apply reports in `$extensions["press.respira"]` |
|
|
420
420
|
| `wordpress_check_design` | Design check: deterministic slop-and-drift rules, plus `rendered: true` (plugin 8.6.20+) for contrast, structure and painted colors on the real page, screenshots attached |
|
|
421
|
-
| `wordpress_mint_design_preview` | Mint a
|
|
421
|
+
| `wordpress_mint_design_preview` | Mint a signed preview URL (plugin 8.6.22+) that renders the real page, drafts and duplicates included, frameable by the respira.press dashboard only; 15 minutes, or `ttl_days` (1 to 90) for a link a person will open; stores nothing |
|
|
422
|
+
| `wordpress_create_share_link` | Create a share link (plugin 8.8.33+) for any page, draft or duplicate: opens with no WordPress login for `ttl_days` (1 to 90, default 14), no write; send it to the client or whoever signs off |
|
|
422
423
|
| `wordpress_get_design_apply_reports` | Read the stored per-builder apply reports for a direction: what apply actually wrote, straight from the site |
|
|
423
424
|
|
|
424
425
|
### Bricks Deep Intelligence (20 tools)
|
package/TOOL_CATALOG.md
CHANGED
|
@@ -2,31 +2,31 @@
|
|
|
2
2
|
|
|
3
3
|
<!-- Generated by scripts/check-tool-registry-parity.mjs. Do not edit by hand. -->
|
|
4
4
|
|
|
5
|
-
Generated catalog: **
|
|
5
|
+
Generated catalog: **194 core tools**, **105 WooCommerce tools**, **299 total**.
|
|
6
6
|
|
|
7
7
|
The runtime applies site context and per-site governance, so an individual connection may expose a smaller relevant subset.
|
|
8
8
|
|
|
9
9
|
## WordPress and Respira
|
|
10
10
|
|
|
11
11
|
- `respira_duplicate_with_translations` — write; hosted/stdio
|
|
12
|
-
- `wordpress_abilities_gap_report` — read; hosted
|
|
12
|
+
- `wordpress_abilities_gap_report` — read; native + hosted
|
|
13
13
|
- `wordpress_activate_design_direction` — write; native + hosted
|
|
14
14
|
- `wordpress_activate_plugin` — write; native + hosted
|
|
15
15
|
- `wordpress_analyze_aeo` — read; native + hosted
|
|
16
16
|
- `wordpress_analyze_images` — read; native + hosted
|
|
17
|
-
- `wordpress_analyze_pagespeed` — read; hosted
|
|
17
|
+
- `wordpress_analyze_pagespeed` — read; native + hosted
|
|
18
18
|
- `wordpress_analyze_performance` — read; native + hosted
|
|
19
19
|
- `wordpress_analyze_rankmath` — read; native + hosted
|
|
20
20
|
- `wordpress_analyze_readability` — read; native + hosted
|
|
21
21
|
- `wordpress_analyze_seo` — read; native + hosted
|
|
22
|
-
- `wordpress_append_theme_file` — read; hosted
|
|
23
|
-
- `wordpress_apply_accessibility_fixes` — write; hosted
|
|
22
|
+
- `wordpress_append_theme_file` — read; native + hosted
|
|
23
|
+
- `wordpress_apply_accessibility_fixes` — write; native + hosted
|
|
24
24
|
- `wordpress_apply_builder_patch` — write; native + hosted
|
|
25
25
|
- `wordpress_apply_design_direction` — write; native + hosted
|
|
26
|
-
- `wordpress_apply_divi_library_item` — write; hosted
|
|
26
|
+
- `wordpress_apply_divi_library_item` — write; native + hosted
|
|
27
27
|
- `wordpress_assign_menu_location` — write; native + hosted
|
|
28
28
|
- `wordpress_batch_update` — write; native + hosted
|
|
29
|
-
- `wordpress_begin_session` — read; hosted
|
|
29
|
+
- `wordpress_begin_session` — read; native + hosted
|
|
30
30
|
- `wordpress_build_mega_menu` — write; native + hosted
|
|
31
31
|
- `wordpress_build_page` — write; native + hosted
|
|
32
32
|
- `wordpress_bulk_pages_operation` — write; native + hosted
|
|
@@ -34,7 +34,7 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
34
34
|
- `wordpress_check_seo_issues` — read; native + hosted
|
|
35
35
|
- `wordpress_check_structured_data` — read; native + hosted
|
|
36
36
|
- `wordpress_convert_html_to_builder` — write; native + hosted
|
|
37
|
-
- `wordpress_create_acf_field_group` — write; hosted
|
|
37
|
+
- `wordpress_create_acf_field_group` — write; native + hosted
|
|
38
38
|
- `wordpress_create_comment` — write; native + hosted
|
|
39
39
|
- `wordpress_create_custom_post` — write; native + hosted
|
|
40
40
|
- `wordpress_create_design_token` — write; native + hosted
|
|
@@ -43,23 +43,24 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
43
43
|
- `wordpress_create_page_duplicate` — write; native + hosted
|
|
44
44
|
- `wordpress_create_playbook` — write; hosted/stdio
|
|
45
45
|
- `wordpress_create_post_duplicate` — write; native + hosted
|
|
46
|
-
- `wordpress_create_post_type` — write; hosted
|
|
46
|
+
- `wordpress_create_post_type` — write; native + hosted
|
|
47
|
+
- `wordpress_create_share_link` — write; native + hosted
|
|
47
48
|
- `wordpress_create_site_navigation` — write; native + hosted
|
|
48
49
|
- `wordpress_create_site_pattern` — write; native + hosted
|
|
49
50
|
- `wordpress_create_site_template` — write; native + hosted
|
|
50
|
-
- `wordpress_create_taxonomy` — write; hosted
|
|
51
|
+
- `wordpress_create_taxonomy` — write; native + hosted
|
|
51
52
|
- `wordpress_create_term` — write; native + hosted
|
|
52
53
|
- `wordpress_create_theme_builder_template` — write; native + hosted
|
|
53
54
|
- `wordpress_create_translation` — write; native + hosted
|
|
54
55
|
- `wordpress_create_user` — write; native + hosted
|
|
55
56
|
- `wordpress_deactivate_design_direction` — write; native + hosted
|
|
56
57
|
- `wordpress_deactivate_plugin` — write; native + hosted
|
|
57
|
-
- `wordpress_delete_acf_field_group` — write; hosted
|
|
58
|
+
- `wordpress_delete_acf_field_group` — write; native + hosted
|
|
58
59
|
- `wordpress_delete_comment` — write; native + hosted
|
|
59
60
|
- `wordpress_delete_custom_post` — write; native + hosted
|
|
60
61
|
- `wordpress_delete_design_direction` — write; native + hosted
|
|
61
62
|
- `wordpress_delete_design_token` — write; native + hosted
|
|
62
|
-
- `wordpress_delete_divi_preset` — write; hosted
|
|
63
|
+
- `wordpress_delete_divi_preset` — write; native + hosted
|
|
63
64
|
- `wordpress_delete_media` — write; native + hosted
|
|
64
65
|
- `wordpress_delete_menu` — write; native + hosted
|
|
65
66
|
- `wordpress_delete_menu_item` — write; native + hosted
|
|
@@ -68,37 +69,37 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
68
69
|
- `wordpress_delete_playbook` — write; hosted/stdio
|
|
69
70
|
- `wordpress_delete_plugin` — write; native + hosted
|
|
70
71
|
- `wordpress_delete_post` — write; native + hosted
|
|
71
|
-
- `wordpress_delete_post_type` — write; hosted
|
|
72
|
+
- `wordpress_delete_post_type` — write; native + hosted
|
|
72
73
|
- `wordpress_delete_site_navigation` — write; native + hosted
|
|
73
74
|
- `wordpress_delete_site_pattern` — write; native + hosted
|
|
74
|
-
- `wordpress_delete_taxonomy` — write; hosted
|
|
75
|
+
- `wordpress_delete_taxonomy` — write; native + hosted
|
|
75
76
|
- `wordpress_delete_term` — write; native + hosted
|
|
76
77
|
- `wordpress_delete_user` — write; native + hosted
|
|
77
78
|
- `wordpress_diagnose_connection` — read; hosted/stdio
|
|
78
79
|
- `wordpress_diff_snapshots` — read; native + hosted
|
|
79
80
|
- `wordpress_duplicate_element` — write; native + hosted
|
|
80
|
-
- `wordpress_end_session` — read; hosted
|
|
81
|
+
- `wordpress_end_session` — read; native + hosted
|
|
81
82
|
- `wordpress_export_design_direction` — read; native + hosted
|
|
82
83
|
- `wordpress_extract_builder_content` — read; native + hosted
|
|
83
84
|
- `wordpress_find_builder_targets` — read; native + hosted
|
|
84
85
|
- `wordpress_find_element` — read; native + hosted
|
|
85
86
|
- `wordpress_forget` — read; native + hosted
|
|
86
|
-
- `wordpress_get_accessibility_scan` — read; hosted
|
|
87
|
+
- `wordpress_get_accessibility_scan` — read; native + hosted
|
|
87
88
|
- `wordpress_get_active_site` — read; hosted/stdio
|
|
88
89
|
- `wordpress_get_activity` — read; native + hosted
|
|
89
90
|
- `wordpress_get_builder_info` — read; native + hosted
|
|
90
|
-
- `wordpress_get_builder_inline_schemas` — read; hosted
|
|
91
|
+
- `wordpress_get_builder_inline_schemas` — read; native + hosted
|
|
91
92
|
- `wordpress_get_comment` — read; native + hosted
|
|
92
93
|
- `wordpress_get_core_web_vitals` — read; native + hosted
|
|
93
94
|
- `wordpress_get_custom_post` — read; native + hosted
|
|
94
95
|
- `wordpress_get_design_apply_reports` — read; native + hosted
|
|
95
96
|
- `wordpress_get_design_direction` — read; native + hosted
|
|
96
|
-
- `wordpress_get_divi_migration_readiness` — read; hosted
|
|
97
|
+
- `wordpress_get_divi_migration_readiness` — read; native + hosted
|
|
97
98
|
- `wordpress_get_media` — read; native + hosted
|
|
98
99
|
- `wordpress_get_menu` — read; native + hosted
|
|
99
100
|
- `wordpress_get_menu_item` — read; native + hosted
|
|
100
101
|
- `wordpress_get_option` — read; native + hosted
|
|
101
|
-
- `wordpress_get_page_outline` — read; hosted
|
|
102
|
+
- `wordpress_get_page_outline` — read; native + hosted
|
|
102
103
|
- `wordpress_get_playbook` — read; hosted/stdio
|
|
103
104
|
- `wordpress_get_post_type` — read; native + hosted
|
|
104
105
|
- `wordpress_get_server_compatibility` — read; native + hosted
|
|
@@ -116,16 +117,16 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
116
117
|
- `wordpress_inject_builder_content` — write; native + hosted
|
|
117
118
|
- `wordpress_install_plugin` — write; native + hosted
|
|
118
119
|
- `wordpress_invoke_ability` — write; hosted/stdio
|
|
119
|
-
- `wordpress_list_accessibility_scans` — read; hosted
|
|
120
|
-
- `wordpress_list_acf_field_groups` — read; hosted
|
|
120
|
+
- `wordpress_list_accessibility_scans` — read; native + hosted
|
|
121
|
+
- `wordpress_list_acf_field_groups` — read; native + hosted
|
|
121
122
|
- `wordpress_list_activity` — read; native + hosted
|
|
122
123
|
- `wordpress_list_comments` — read; native + hosted
|
|
123
|
-
- `wordpress_list_custom_post_types` — read; hosted
|
|
124
|
+
- `wordpress_list_custom_post_types` — read; native + hosted
|
|
124
125
|
- `wordpress_list_custom_posts` — read; native + hosted
|
|
125
|
-
- `wordpress_list_custom_taxonomies` — read; hosted
|
|
126
|
+
- `wordpress_list_custom_taxonomies` — read; native + hosted
|
|
126
127
|
- `wordpress_list_design_directions` — read; native + hosted
|
|
127
128
|
- `wordpress_list_design_tokens` — read; native + hosted
|
|
128
|
-
- `wordpress_list_divi_presets` — read; hosted
|
|
129
|
+
- `wordpress_list_divi_presets` — read; native + hosted
|
|
129
130
|
- `wordpress_list_media` — read; native + hosted
|
|
130
131
|
- `wordpress_list_memory` — read; native + hosted
|
|
131
132
|
- `wordpress_list_menu_items` — read; native + hosted
|
|
@@ -146,40 +147,40 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
146
147
|
- `wordpress_list_terms` — read; native + hosted
|
|
147
148
|
- `wordpress_list_theme_builder_templates` — read; native + hosted
|
|
148
149
|
- `wordpress_list_users` — read; native + hosted
|
|
149
|
-
- `wordpress_make_responsive` — read; hosted
|
|
150
|
+
- `wordpress_make_responsive` — read; native + hosted
|
|
150
151
|
- `wordpress_mint_design_preview` — read; native + hosted
|
|
151
152
|
- `wordpress_move_element` — write; native + hosted
|
|
152
153
|
- `wordpress_purge_cache` — write; native + hosted
|
|
153
154
|
- `wordpress_read_page` — read; native + hosted
|
|
154
155
|
- `wordpress_read_post` — read; native + hosted
|
|
155
|
-
- `wordpress_read_theme_file` — read; hosted
|
|
156
|
+
- `wordpress_read_theme_file` — read; native + hosted
|
|
156
157
|
- `wordpress_redeem_token` — write; hosted/stdio
|
|
157
158
|
- `wordpress_remember` — read; native + hosted
|
|
158
159
|
- `wordpress_remove_element` — write; native + hosted
|
|
159
160
|
- `wordpress_reorder_elements` — write; native + hosted
|
|
160
161
|
- `wordpress_report_issue` — read; hosted/stdio
|
|
161
162
|
- `wordpress_reset_site_template` — write; native + hosted
|
|
162
|
-
- `wordpress_resolve_divi_preset` — read; hosted
|
|
163
|
-
- `wordpress_restore_session` — write; hosted
|
|
163
|
+
- `wordpress_resolve_divi_preset` — read; native + hosted
|
|
164
|
+
- `wordpress_restore_session` — write; native + hosted
|
|
164
165
|
- `wordpress_restore_snapshot` — write; native + hosted
|
|
165
|
-
- `wordpress_run_pagespeed_audit` — read; hosted
|
|
166
|
+
- `wordpress_run_pagespeed_audit` — read; native + hosted
|
|
166
167
|
- `wordpress_run_security_audit` — read; native + hosted
|
|
167
168
|
- `wordpress_save_design_direction` — write; native + hosted
|
|
168
|
-
- `wordpress_save_divi_library_item` — write; hosted
|
|
169
|
-
- `wordpress_scan_page_accessibility` — read; hosted
|
|
169
|
+
- `wordpress_save_divi_library_item` — write; native + hosted
|
|
170
|
+
- `wordpress_scan_page_accessibility` — read; native + hosted
|
|
170
171
|
- `wordpress_search_abilities` — read; native + hosted
|
|
171
172
|
- `wordpress_search_docs` — read; hosted/stdio
|
|
172
173
|
- `wordpress_search_stock_images` — read; native + hosted
|
|
173
174
|
- `wordpress_sideload_image` — write; native + hosted
|
|
174
175
|
- `wordpress_switch_site` — write; native + hosted
|
|
175
|
-
- `wordpress_update_acf_field_group` — write; hosted
|
|
176
|
+
- `wordpress_update_acf_field_group` — write; native + hosted
|
|
176
177
|
- `wordpress_update_comment` — write; native + hosted
|
|
177
178
|
- `wordpress_update_core_security` — write; native + hosted
|
|
178
179
|
- `wordpress_update_custom_post` — write; native + hosted
|
|
179
180
|
- `wordpress_update_design_token` — write; native + hosted
|
|
180
181
|
- `wordpress_update_element` — write; native + hosted
|
|
181
182
|
- `wordpress_update_media` — write; native + hosted
|
|
182
|
-
- `wordpress_update_media_batch` — write; hosted
|
|
183
|
+
- `wordpress_update_media_batch` — write; native + hosted
|
|
183
184
|
- `wordpress_update_menu` — write; native + hosted
|
|
184
185
|
- `wordpress_update_menu_item` — write; native + hosted
|
|
185
186
|
- `wordpress_update_module` — write; native + hosted
|
|
@@ -188,19 +189,19 @@ The runtime applies site context and per-site governance, so an individual conne
|
|
|
188
189
|
- `wordpress_update_playbook` — write; hosted/stdio
|
|
189
190
|
- `wordpress_update_plugin` — write; native + hosted
|
|
190
191
|
- `wordpress_update_post` — write; native + hosted
|
|
191
|
-
- `wordpress_update_post_type` — write; hosted
|
|
192
|
+
- `wordpress_update_post_type` — write; native + hosted
|
|
192
193
|
- `wordpress_update_site_navigation` — write; native + hosted
|
|
193
194
|
- `wordpress_update_site_pattern` — write; native + hosted
|
|
194
195
|
- `wordpress_update_site_template` — write; native + hosted
|
|
195
|
-
- `wordpress_update_taxonomy` — write; hosted
|
|
196
|
+
- `wordpress_update_taxonomy` — write; native + hosted
|
|
196
197
|
- `wordpress_update_term` — write; native + hosted
|
|
197
198
|
- `wordpress_update_theme_builder_template` — write; native + hosted
|
|
198
199
|
- `wordpress_update_translation` — write; native + hosted
|
|
199
200
|
- `wordpress_update_user` — write; native + hosted
|
|
200
201
|
- `wordpress_upload_media` — write; native + hosted
|
|
201
|
-
- `wordpress_upsert_divi_presets` — read; hosted
|
|
202
|
+
- `wordpress_upsert_divi_presets` — read; native + hosted
|
|
202
203
|
- `wordpress_validate_security` — read; native + hosted
|
|
203
|
-
- `wordpress_write_theme_file` — read; hosted
|
|
204
|
+
- `wordpress_write_theme_file` — read; native + hosted
|
|
204
205
|
|
|
205
206
|
## WooCommerce add-on
|
|
206
207
|
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket 0e6bd33c — redeeming a token must not silently discard the per-site
|
|
3
|
+
* settings the token does not own.
|
|
4
|
+
*
|
|
5
|
+
* A redeem token carries exactly five fields per site: id, name, url, apiKey
|
|
6
|
+
* and default (buildRespiraConfig, product-website/src/lib/mcp-config.ts).
|
|
7
|
+
* Everything else on a site object in ~/.respira/config.json was put there by
|
|
8
|
+
* the operator, usually on this connector's own advice:
|
|
9
|
+
*
|
|
10
|
+
* forceRestRoute — the only thing that makes hosts work where pretty
|
|
11
|
+
* /wp-json/ 404s and ?rest_route= returns JSON
|
|
12
|
+
* forceWriteMethod — the only thing that makes hosts work where PUT is
|
|
13
|
+
* blocked or dropped
|
|
14
|
+
* httpAuth — server-level Basic Auth for staging sites
|
|
15
|
+
* insecureTLS — the documented TLS escape hatch
|
|
16
|
+
*
|
|
17
|
+
* mergeRedeemedSites used to do `byKey.set(key, site)` on a URL collision:
|
|
18
|
+
* the freshly minted entry replaced the whole object. Redeem reported
|
|
19
|
+
* success, the file was rewritten, and every one of those flags was gone.
|
|
20
|
+
* The session kept working (the live clients were already built) so nothing
|
|
21
|
+
* failed until the next restart, at which point the site failed exactly the
|
|
22
|
+
* way it had failed before the flag was added.
|
|
23
|
+
*
|
|
24
|
+
* These assertions fail on unmodified main.
|
|
25
|
+
*/
|
|
26
|
+
import { test } from 'node:test';
|
|
27
|
+
import assert from 'node:assert/strict';
|
|
28
|
+
import { mergeRedeemedSites } from '../config.js';
|
|
29
|
+
/** What the dashboard mints: five fields, nothing else. */
|
|
30
|
+
function tokenSite(id, url, extra = {}) {
|
|
31
|
+
return { id, name: id, url, apiKey: `respira_site_${id}`, ...extra };
|
|
32
|
+
}
|
|
33
|
+
test('a re-redeem keeps forceRestRoute on the site that needed it', () => {
|
|
34
|
+
const existing = [
|
|
35
|
+
tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
|
|
36
|
+
];
|
|
37
|
+
const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
|
|
38
|
+
assert.equal(merged.length, 1);
|
|
39
|
+
assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute was dropped by the redeem rewrite; the site 404s on every call after the next restart');
|
|
40
|
+
});
|
|
41
|
+
test('a re-redeem keeps every per-site key the token does not carry', () => {
|
|
42
|
+
const existing = [
|
|
43
|
+
tokenSite('a', 'https://a.example.com', {
|
|
44
|
+
default: true,
|
|
45
|
+
forceRestRoute: true,
|
|
46
|
+
forceWriteMethod: 'post',
|
|
47
|
+
insecureTLS: true,
|
|
48
|
+
httpAuth: { username: 'staging', password: 'hunter2' },
|
|
49
|
+
}),
|
|
50
|
+
];
|
|
51
|
+
const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
|
|
52
|
+
assert.equal(merged[0].forceRestRoute, true, 'forceRestRoute dropped');
|
|
53
|
+
assert.equal(merged[0].forceWriteMethod, 'post', 'forceWriteMethod dropped');
|
|
54
|
+
assert.equal(merged[0].insecureTLS, true, 'insecureTLS dropped');
|
|
55
|
+
assert.deepEqual(merged[0].httpAuth, { username: 'staging', password: 'hunter2' }, 'httpAuth dropped');
|
|
56
|
+
});
|
|
57
|
+
test('an unrecognised per-site key added by hand also survives', () => {
|
|
58
|
+
// Deliberately not in WordPressSiteConfig: an allowlist-based fix would
|
|
59
|
+
// drop this, and would drop every future key added to the schema too.
|
|
60
|
+
const existing = [
|
|
61
|
+
{ ...tokenSite('a', 'https://a.example.com', { default: true }), someFutureFlag: 'keep-me' },
|
|
62
|
+
];
|
|
63
|
+
const merged = mergeRedeemedSites(existing, [tokenSite('a', 'https://a.example.com', { default: true })]);
|
|
64
|
+
assert.equal(merged[0].someFutureFlag, 'keep-me');
|
|
65
|
+
});
|
|
66
|
+
test('the token still wins for the fields it does carry', () => {
|
|
67
|
+
const existing = [
|
|
68
|
+
tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
|
|
69
|
+
];
|
|
70
|
+
const merged = mergeRedeemedSites(existing, [
|
|
71
|
+
{ id: 'a', name: 'Renamed', url: 'https://a.example.com', apiKey: 'respira_site_rotated', default: true },
|
|
72
|
+
]);
|
|
73
|
+
assert.equal(merged[0].apiKey, 'respira_site_rotated', 'a rotated key must still land');
|
|
74
|
+
assert.equal(merged[0].name, 'Renamed', 'a renamed site must still land');
|
|
75
|
+
assert.equal(merged[0].forceRestRoute, true, 'and the local flag must still survive it');
|
|
76
|
+
});
|
|
77
|
+
test('preserving local keys does not leave two sites claiming default', () => {
|
|
78
|
+
const existing = [
|
|
79
|
+
tokenSite('a', 'https://a.example.com', { default: true, forceRestRoute: true }),
|
|
80
|
+
tokenSite('b', 'https://b.example.com'),
|
|
81
|
+
];
|
|
82
|
+
// A full-account token re-mints both and marks b default.
|
|
83
|
+
const merged = mergeRedeemedSites(existing, [
|
|
84
|
+
tokenSite('b', 'https://b.example.com', { default: true }),
|
|
85
|
+
tokenSite('a', 'https://a.example.com'),
|
|
86
|
+
]);
|
|
87
|
+
assert.equal(merged.filter((s) => s.default === true).length, 1, 'exactly one default must survive the merge');
|
|
88
|
+
assert.equal(merged.find((s) => s.default === true)?.url, 'https://b.example.com', "the just-redeemed token's default wins");
|
|
89
|
+
assert.equal(merged.find((s) => s.url === 'https://a.example.com')?.forceRestRoute, true);
|
|
90
|
+
});
|
|
91
|
+
//# sourceMappingURL=0e6bd33c-redeem-preserves-per-site-settings.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"0e6bd33c-redeem-preserves-per-site-settings.test.js","sourceRoot":"","sources":["../../src/__tests__/0e6bd33c-redeem-preserves-per-site-settings.test.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
|
|
3
|
+
* dedicated client methods that call `client.put()` directly.
|
|
4
|
+
*
|
|
5
|
+
* Two earlier tickets fixed this in two places: e2d884cb wired
|
|
6
|
+
* `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
|
|
7
|
+
* 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
|
|
8
|
+
* left the dedicated methods alone. On a host whose edge blocks PUT (reported
|
|
9
|
+
* on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
|
|
10
|
+
* `write_method_probe.put_status` 404/405) that produced a very specific
|
|
11
|
+
* shape of broken: `respira_create_menu_item` worked, because creating is a
|
|
12
|
+
* POST, and `respira_update_menu_item` returned 405, so a menu could be built
|
|
13
|
+
* and then never reordered.
|
|
14
|
+
*
|
|
15
|
+
* `respira_update_menu_item` is the one that got reported. It is not special:
|
|
16
|
+
* every menu, media, user, comment, term, WooCommerce and ACF update method
|
|
17
|
+
* had the identical gap.
|
|
18
|
+
*
|
|
19
|
+
* The fallback is safe here for the same reason it is safe for posts and
|
|
20
|
+
* pages: the plugin registers all of these routes with
|
|
21
|
+
* `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
|
|
22
|
+
* POST lands on the identical handler. Verified against the route table in
|
|
23
|
+
* wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
|
|
24
|
+
*
|
|
25
|
+
* A real HTTP server, not a mocked adapter, so the whole interceptor chain
|
|
26
|
+
* runs — including the codedError() step that strips `.response` and made an
|
|
27
|
+
* earlier status-only version of this predicate a no-op.
|
|
28
|
+
*/
|
|
29
|
+
export {};
|
|
30
|
+
//# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.d.ts","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG"}
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Ticket 139f7353 — the PUT-blocked-host fallback never reached the ~25
|
|
3
|
+
* dedicated client methods that call `client.put()` directly.
|
|
4
|
+
*
|
|
5
|
+
* Two earlier tickets fixed this in two places: e2d884cb wired
|
|
6
|
+
* `putWithPostFallback` into updatePost/updatePage/updateCustomPost, and
|
|
7
|
+
* 2b3a706b wired it into the shared `callRestV1`/`callRestV2` callers. Both
|
|
8
|
+
* left the dedicated methods alone. On a host whose edge blocks PUT (reported
|
|
9
|
+
* on Kinsta + Cloudflare, where `respira_diagnose_connection` shows
|
|
10
|
+
* `write_method_probe.put_status` 404/405) that produced a very specific
|
|
11
|
+
* shape of broken: `respira_create_menu_item` worked, because creating is a
|
|
12
|
+
* POST, and `respira_update_menu_item` returned 405, so a menu could be built
|
|
13
|
+
* and then never reordered.
|
|
14
|
+
*
|
|
15
|
+
* `respira_update_menu_item` is the one that got reported. It is not special:
|
|
16
|
+
* every menu, media, user, comment, term, WooCommerce and ACF update method
|
|
17
|
+
* had the identical gap.
|
|
18
|
+
*
|
|
19
|
+
* The fallback is safe here for the same reason it is safe for posts and
|
|
20
|
+
* pages: the plugin registers all of these routes with
|
|
21
|
+
* `WP_REST_Server::EDITABLE`, which is `'POST, PUT, PATCH'`, so the fallback
|
|
22
|
+
* POST lands on the identical handler. Verified against the route table in
|
|
23
|
+
* wordpress-plugin/respira-for-wordpress/includes/class-respira-api.php.
|
|
24
|
+
*
|
|
25
|
+
* A real HTTP server, not a mocked adapter, so the whole interceptor chain
|
|
26
|
+
* runs — including the codedError() step that strips `.response` and made an
|
|
27
|
+
* earlier status-only version of this predicate a no-op.
|
|
28
|
+
*/
|
|
29
|
+
import { test, after } from 'node:test';
|
|
30
|
+
import assert from 'node:assert/strict';
|
|
31
|
+
import http from 'node:http';
|
|
32
|
+
import { WordPressClient, ORIGINAL_METHOD_HEADER, isRetryableAsPost } from '../wordpress-client.js';
|
|
33
|
+
/**
|
|
34
|
+
* The second half of the same ticket. PUT_FALLBACK_STATUSES has said
|
|
35
|
+
* {403, 404, 405} since 2b3a706b, but `handleError` rebuilds every non-2xx
|
|
36
|
+
* through `codedError`, which drops `.response`. So the status check in
|
|
37
|
+
* `isRetryableAsPost` could never fire on a real client error, and the
|
|
38
|
+
* name-based check that patched around that was hardcoded to 403 alone.
|
|
39
|
+
* A host that answers PUT with 405 and an HTML page — which is what the
|
|
40
|
+
* reporter's edge does — was therefore never retried, even at the call sites
|
|
41
|
+
* where the fallback WAS already wired in.
|
|
42
|
+
*/
|
|
43
|
+
test('an HTML 405 from the edge is retryable as POST, not just an HTML 403', () => {
|
|
44
|
+
assert.equal(isRetryableAsPost({ name: 'http_403_non_wordpress' }), true);
|
|
45
|
+
assert.equal(isRetryableAsPost({ name: 'http_405_non_wordpress' }), true, '405 is the reported case');
|
|
46
|
+
assert.equal(isRetryableAsPost({ name: 'http_404_non_wordpress' }), true, '404 is the other reported case');
|
|
47
|
+
// A status raised BY WordPress keeps its WP_Error code and stays a decision.
|
|
48
|
+
assert.equal(isRetryableAsPost({ name: 'rest_forbidden' }), false);
|
|
49
|
+
assert.equal(isRetryableAsPost({ name: 'http_500_non_wordpress' }), false);
|
|
50
|
+
assert.equal(isRetryableAsPost({ name: 'http_401_non_wordpress' }), false);
|
|
51
|
+
});
|
|
52
|
+
const servers = [];
|
|
53
|
+
after(() => { for (const s of servers)
|
|
54
|
+
s.close(); });
|
|
55
|
+
/**
|
|
56
|
+
* A host that answers 405 Method Not Allowed to PUT before WordPress runs
|
|
57
|
+
* (Cloudflare / Kinsta edge rule on the verb) and 200 to POST on the same
|
|
58
|
+
* path. The 405 body is HTML, like a real edge rejection, so the client
|
|
59
|
+
* cannot mistake it for a WordPress decision.
|
|
60
|
+
*/
|
|
61
|
+
async function startPutBlockedSite() {
|
|
62
|
+
const hits = [];
|
|
63
|
+
const server = http.createServer((req, res) => {
|
|
64
|
+
hits.push({
|
|
65
|
+
method: req.method || '',
|
|
66
|
+
url: req.url || '',
|
|
67
|
+
idempotencyKey: req.headers['idempotency-key'] || null,
|
|
68
|
+
originalMethod: req.headers[ORIGINAL_METHOD_HEADER.toLowerCase()] || null,
|
|
69
|
+
});
|
|
70
|
+
if (req.method === 'PUT') {
|
|
71
|
+
res.writeHead(405, { 'Content-Type': 'text/html', Allow: 'GET, POST, HEAD' });
|
|
72
|
+
res.end('<!DOCTYPE html><html><head><title>405 Not Allowed</title></head><body>405</body></html>');
|
|
73
|
+
return;
|
|
74
|
+
}
|
|
75
|
+
if (req.method === 'POST') {
|
|
76
|
+
let body = '';
|
|
77
|
+
req.on('data', (c) => { body += c; });
|
|
78
|
+
req.on('end', () => {
|
|
79
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
80
|
+
res.end(JSON.stringify({ success: true, via: 'POST', echo: body ? JSON.parse(body) : null }));
|
|
81
|
+
});
|
|
82
|
+
return;
|
|
83
|
+
}
|
|
84
|
+
res.writeHead(200, { 'Content-Type': 'application/json' });
|
|
85
|
+
res.end(JSON.stringify({ ok: true }));
|
|
86
|
+
});
|
|
87
|
+
servers.push(server);
|
|
88
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
89
|
+
const { port } = server.address();
|
|
90
|
+
const client = new WordPressClient({
|
|
91
|
+
id: 'blocked',
|
|
92
|
+
name: 'blocked',
|
|
93
|
+
url: `http://127.0.0.1:${port}`,
|
|
94
|
+
apiKey: `respira_site_${'x'.repeat(24)}`,
|
|
95
|
+
});
|
|
96
|
+
return { client, hits };
|
|
97
|
+
}
|
|
98
|
+
test('update_menu_item survives a host that answers 405 to PUT', async () => {
|
|
99
|
+
const { client, hits } = await startPutBlockedSite();
|
|
100
|
+
const result = await client.updateMenuItem(42, { position: 2 });
|
|
101
|
+
assert.equal(result.success, true, 'the reorder must actually land, not surface a 405');
|
|
102
|
+
assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST'], 'exactly one PUT then exactly one POST');
|
|
103
|
+
assert.equal(hits[1].url, '/wp-json/respira/v1/menus/items/42');
|
|
104
|
+
assert.equal(hits[1].originalMethod, 'PUT', 'the fallback POST must declare the verb it replaces');
|
|
105
|
+
assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey, 'both attempts must share one Idempotency-Key or the plugin cannot dedupe them');
|
|
106
|
+
assert.ok(hits[0].idempotencyKey, 'an idempotency key must be present at all');
|
|
107
|
+
});
|
|
108
|
+
/**
|
|
109
|
+
* update_menu_item is the reported one. These are the sibling methods that had
|
|
110
|
+
* the identical gap — all of them EDITABLE routes, so all of them fixable the
|
|
111
|
+
* same way. Named individually so a future regression names the tool it broke.
|
|
112
|
+
*/
|
|
113
|
+
const SIBLINGS = [
|
|
114
|
+
['update_menu', (c) => c.updateMenu(7, { name: 'Main' }), '/wp-json/respira/v1/menus/7'],
|
|
115
|
+
['assign_menu_location', (c) => c.assignMenuToLocation('primary', 7), '/wp-json/respira/v1/menus/locations/primary'],
|
|
116
|
+
['update_media', (c) => c.updateMedia(3, { alt: 'a' }), '/wp-json/respira/v1/media/3'],
|
|
117
|
+
['update_media_batch', (c) => c.updateMediaBatch([{ id: 3, alt: 'a' }]), '/wp-json/respira/v1/media/batch'],
|
|
118
|
+
['update_user', (c) => c.updateUser(5, { display_name: 'A' }), '/wp-json/respira/v1/users/5'],
|
|
119
|
+
['update_comment', (c) => c.updateComment(9, { content: 'x' }), '/wp-json/respira/v1/comments/9'],
|
|
120
|
+
['update_term', (c) => c.updateTerm('category', 4, { name: 'n' }), '/wp-json/respira/v1/taxonomies/category/terms/4'],
|
|
121
|
+
];
|
|
122
|
+
for (const [label, call, expectedPath] of SIBLINGS) {
|
|
123
|
+
test(`${label} survives a host that answers 405 to PUT`, async () => {
|
|
124
|
+
const { client, hits } = await startPutBlockedSite();
|
|
125
|
+
const result = await call(client);
|
|
126
|
+
assert.equal(result.success, true, `${label} must fall back to POST`);
|
|
127
|
+
assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
|
|
128
|
+
assert.equal(hits[1].url, expectedPath);
|
|
129
|
+
assert.equal(hits[1].originalMethod, 'PUT');
|
|
130
|
+
assert.equal(hits[0].idempotencyKey, hits[1].idempotencyKey);
|
|
131
|
+
});
|
|
132
|
+
}
|
|
133
|
+
test('the ACF dispatcher falls back too', async () => {
|
|
134
|
+
const { client, hits } = await startPutBlockedSite();
|
|
135
|
+
const result = await client.dispatchAcfTool('respira_acf_update_field', {
|
|
136
|
+
post_id: 12,
|
|
137
|
+
field_key: 'field_1',
|
|
138
|
+
value: 'x',
|
|
139
|
+
});
|
|
140
|
+
assert.equal(result.success, true);
|
|
141
|
+
assert.deepEqual(hits.map((h) => h.method), ['PUT', 'POST']);
|
|
142
|
+
assert.equal(hits[1].originalMethod, 'PUT');
|
|
143
|
+
});
|
|
144
|
+
test('a WordPress-issued 405 on a POST is not retried into a loop', async () => {
|
|
145
|
+
// Guard against the fix turning one failed call into an unbounded ladder:
|
|
146
|
+
// the fallback is exactly one POST, and a POST that fails is final.
|
|
147
|
+
const hits = [];
|
|
148
|
+
const server = http.createServer((req, res) => {
|
|
149
|
+
hits.push(req.method || '');
|
|
150
|
+
res.writeHead(405, { 'Content-Type': 'text/html' });
|
|
151
|
+
res.end('<!DOCTYPE html><html><head><title>405</title></head><body>405</body></html>');
|
|
152
|
+
});
|
|
153
|
+
servers.push(server);
|
|
154
|
+
await new Promise((resolve) => server.listen(0, '127.0.0.1', resolve));
|
|
155
|
+
const { port } = server.address();
|
|
156
|
+
const client = new WordPressClient({
|
|
157
|
+
id: 'x', name: 'x', url: `http://127.0.0.1:${port}`, apiKey: `respira_site_${'x'.repeat(24)}`,
|
|
158
|
+
});
|
|
159
|
+
await assert.rejects(() => client.updateMenuItem(1, { position: 1 }));
|
|
160
|
+
assert.deepEqual(hits, ['PUT', 'POST'], 'one PUT, one POST, then stop');
|
|
161
|
+
});
|
|
162
|
+
//# sourceMappingURL=139f7353-put-fallback-dedicated-methods.test.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"139f7353-put-fallback-dedicated-methods.test.js","sourceRoot":"","sources":["../../src/__tests__/139f7353-put-fallback-dedicated-methods.test.ts"],"names":[],"mappings":"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"}
|