drupal-mcp-connector 2.7.3 → 2.8.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 (229) hide show
  1. package/.agents/commands/drupal-audit-config-best-practices.md +15 -0
  2. package/{.claude → .agents}/commands/drupal-audit-site-health.md +4 -5
  3. package/.agents/commands/drupal-block-user.md +19 -0
  4. package/{.claude → .agents}/commands/drupal-bulk-create.md +4 -5
  5. package/{.claude → .agents}/commands/drupal-bulk-update.md +4 -5
  6. package/.agents/commands/drupal-config-get.md +18 -0
  7. package/.agents/commands/drupal-config-list.md +16 -0
  8. package/.agents/commands/drupal-config-set.md +19 -0
  9. package/.agents/commands/drupal-content-by-moderation-state.md +21 -0
  10. package/.agents/commands/drupal-create-block.md +20 -0
  11. package/{.claude → .agents}/commands/drupal-create-media.md +4 -5
  12. package/{.claude → .agents}/commands/drupal-create-menu-link.md +4 -5
  13. package/{.claude → .agents}/commands/drupal-create-node.md +7 -8
  14. package/{.claude → .agents}/commands/drupal-create-paragraph.md +4 -5
  15. package/{.claude → .agents}/commands/drupal-create-redirect.md +4 -5
  16. package/.agents/commands/drupal-create-taxonomy-term.md +22 -0
  17. package/{.claude → .agents}/commands/drupal-create-translation.md +4 -5
  18. package/.agents/commands/drupal-create-user.md +23 -0
  19. package/.agents/commands/drupal-delete-media.md +21 -0
  20. package/.agents/commands/drupal-delete-node.md +22 -0
  21. package/.agents/commands/drupal-delete-taxonomy-term.md +21 -0
  22. package/{.claude → .agents}/commands/drupal-describe-fields.md +4 -5
  23. package/.agents/commands/drupal-drush-cache-rebuild.md +15 -0
  24. package/.agents/commands/drupal-drush-config-export.md +15 -0
  25. package/.agents/commands/drupal-drush-config-import.md +15 -0
  26. package/.agents/commands/drupal-drush-config-status.md +15 -0
  27. package/.agents/commands/drupal-drush-cron.md +15 -0
  28. package/.agents/commands/drupal-drush-module-disable.md +20 -0
  29. package/.agents/commands/drupal-drush-module-enable.md +18 -0
  30. package/.agents/commands/drupal-drush-module-list.md +16 -0
  31. package/.agents/commands/drupal-drush-security-updates.md +15 -0
  32. package/{.claude → .agents}/commands/drupal-drush-sql-query.md +4 -5
  33. package/.agents/commands/drupal-drush-status.md +15 -0
  34. package/.agents/commands/drupal-drush-updatedb.md +15 -0
  35. package/.agents/commands/drupal-drush-user-create.md +21 -0
  36. package/.agents/commands/drupal-drush-user-list.md +18 -0
  37. package/.agents/commands/drupal-drush-watchdog.md +18 -0
  38. package/{.claude → .agents}/commands/drupal-entity-create.md +4 -5
  39. package/.agents/commands/drupal-entity-delete.md +23 -0
  40. package/.agents/commands/drupal-entity-get.md +21 -0
  41. package/{.claude → .agents}/commands/drupal-entity-list.md +4 -5
  42. package/{.claude → .agents}/commands/drupal-entity-update.md +7 -8
  43. package/.agents/commands/drupal-find-orphaned-media.md +17 -0
  44. package/.agents/commands/drupal-get-entity-schema.md +19 -0
  45. package/.agents/commands/drupal-get-media.md +19 -0
  46. package/.agents/commands/drupal-get-node.md +19 -0
  47. package/{.claude → .agents}/commands/drupal-get-paragraph.md +4 -5
  48. package/{.claude → .agents}/commands/drupal-get-revision.md +4 -5
  49. package/.agents/commands/drupal-get-taxonomy-term.md +19 -0
  50. package/.agents/commands/drupal-get-taxonomy-terms.md +20 -0
  51. package/.agents/commands/drupal-get-user-by-name.md +18 -0
  52. package/.agents/commands/drupal-get-user.md +18 -0
  53. package/{.claude → .agents}/commands/drupal-governance-status.md +4 -5
  54. package/.agents/commands/drupal-graphql-introspect.md +16 -0
  55. package/{.claude → .agents}/commands/drupal-graphql.md +4 -5
  56. package/{.claude → .agents}/commands/drupal-list-blocks.md +4 -5
  57. package/.agents/commands/drupal-list-content-types.md +15 -0
  58. package/.agents/commands/drupal-list-entity-types.md +15 -0
  59. package/.agents/commands/drupal-list-media-types.md +15 -0
  60. package/.agents/commands/drupal-list-media.md +20 -0
  61. package/{.claude → .agents}/commands/drupal-list-menu-links.md +4 -5
  62. package/.agents/commands/drupal-list-moderation-states.md +19 -0
  63. package/{.claude → .agents}/commands/drupal-list-nodes.md +4 -5
  64. package/{.claude → .agents}/commands/drupal-list-revisions.md +4 -5
  65. package/.agents/commands/drupal-list-roles.md +15 -0
  66. package/{.claude → .agents}/commands/drupal-list-sites.md +2 -3
  67. package/{.claude → .agents}/commands/drupal-list-translations.md +4 -5
  68. package/.agents/commands/drupal-list-users.md +19 -0
  69. package/.agents/commands/drupal-list-vocabularies.md +15 -0
  70. package/.agents/commands/drupal-mcp-whoami.md +15 -0
  71. package/.agents/commands/drupal-report-404-log.md +16 -0
  72. package/.agents/commands/drupal-report-accessibility-audit.md +17 -0
  73. package/.agents/commands/drupal-report-alias-coverage.md +17 -0
  74. package/.agents/commands/drupal-report-broken-embeds.md +17 -0
  75. package/{.claude → .agents}/commands/drupal-report-broken-links.md +4 -5
  76. package/.agents/commands/drupal-report-cache-config.md +15 -0
  77. package/.agents/commands/drupal-report-config-drift.md +15 -0
  78. package/.agents/commands/drupal-report-content-by-author.md +17 -0
  79. package/.agents/commands/drupal-report-content-summary.md +15 -0
  80. package/.agents/commands/drupal-report-duplicate-content.md +17 -0
  81. package/.agents/commands/drupal-report-field-completeness.md +20 -0
  82. package/.agents/commands/drupal-report-menu-integrity.md +16 -0
  83. package/{.claude → .agents}/commands/drupal-report-missing-field.md +4 -5
  84. package/.agents/commands/drupal-report-module-audit.md +15 -0
  85. package/.agents/commands/drupal-report-orphan-pages.md +17 -0
  86. package/{.claude → .agents}/commands/drupal-report-orphaned-references.md +4 -5
  87. package/.agents/commands/drupal-report-permission-audit.md +15 -0
  88. package/.agents/commands/drupal-report-pii-exposure.md +18 -0
  89. package/.agents/commands/drupal-report-readability.md +18 -0
  90. package/.agents/commands/drupal-report-recently-published.md +17 -0
  91. package/.agents/commands/drupal-report-redirect-health.md +16 -0
  92. package/.agents/commands/drupal-report-revision-hotspots.md +17 -0
  93. package/.agents/commands/drupal-report-scheduled-content.md +17 -0
  94. package/{.claude → .agents}/commands/drupal-report-seo-audit.md +4 -5
  95. package/.agents/commands/drupal-report-seo-meta-coverage.md +18 -0
  96. package/.agents/commands/drupal-report-stale-content.md +19 -0
  97. package/.agents/commands/drupal-report-status-report.md +16 -0
  98. package/.agents/commands/drupal-report-taxonomy-usage.md +21 -0
  99. package/.agents/commands/drupal-report-text-format-audit.md +15 -0
  100. package/.agents/commands/drupal-report-translation-coverage.md +18 -0
  101. package/.agents/commands/drupal-report-unpublished.md +17 -0
  102. package/.agents/commands/drupal-report-user-activity.md +17 -0
  103. package/.agents/commands/drupal-report-workflow-bottlenecks.md +19 -0
  104. package/{.claude → .agents}/commands/drupal-resolve-reference.md +4 -5
  105. package/{.claude → .agents}/commands/drupal-revert-revision.md +4 -5
  106. package/{.claude → .agents}/commands/drupal-schedule-publish.md +4 -5
  107. package/.agents/commands/drupal-search-content.md +21 -0
  108. package/.agents/commands/drupal-search.md +20 -0
  109. package/.agents/commands/drupal-security-info.md +15 -0
  110. package/.agents/commands/drupal-set-moderation-state.md +20 -0
  111. package/.agents/commands/drupal-site-info.md +15 -0
  112. package/.agents/commands/drupal-update-media.md +22 -0
  113. package/{.claude → .agents}/commands/drupal-update-menu-link.md +4 -5
  114. package/{.claude → .agents}/commands/drupal-update-node.md +10 -11
  115. package/{.claude → .agents}/commands/drupal-update-paragraph.md +4 -5
  116. package/{.claude → .agents}/commands/drupal-update-redirect.md +4 -5
  117. package/.agents/commands/drupal-update-taxonomy-term.md +22 -0
  118. package/.agents/commands/drupal-update-user.md +24 -0
  119. package/{.claude → .agents}/commands/drupal-upload-file-and-create-media.md +4 -5
  120. package/{.claude → .agents}/commands/drupal-upload-file.md +4 -5
  121. package/CHANGELOG.md +80 -0
  122. package/README.md +23 -12
  123. package/config/config.example.json +1 -1
  124. package/package.json +4 -2
  125. package/scripts/generate-commands.js +42 -21
  126. package/scripts/install-commands.js +157 -0
  127. package/src/lib/backends/backend-interface.js +20 -1
  128. package/src/lib/backends/jsonapi.js +47 -4
  129. package/src/lib/body-summary.js +60 -0
  130. package/src/lib/data-flow.js +437 -0
  131. package/src/lib/dispatch.js +99 -30
  132. package/src/lib/drupal-fetch.js +52 -6
  133. package/src/lib/field-definition.js +285 -0
  134. package/src/lib/operations.js +39 -2
  135. package/src/lib/patch-preflight.js +157 -66
  136. package/src/lib/security.js +15 -1
  137. package/src/lib/server-tools.js +9 -0
  138. package/src/lib/site-target.js +78 -0
  139. package/src/lib/tool-prompts.js +2 -1
  140. package/src/lib/write-revision.js +82 -2
  141. package/src/tools/bulk.js +4 -3
  142. package/src/tools/config.js +5 -2
  143. package/src/tools/entities.js +11 -7
  144. package/src/tools/index.js +24 -3
  145. package/src/tools/moderation.js +88 -9
  146. package/src/tools/nodes.js +55 -43
  147. package/.claude/commands/drupal-audit-config-best-practices.md +0 -16
  148. package/.claude/commands/drupal-block-user.md +0 -20
  149. package/.claude/commands/drupal-config-get.md +0 -19
  150. package/.claude/commands/drupal-config-list.md +0 -17
  151. package/.claude/commands/drupal-config-set.md +0 -20
  152. package/.claude/commands/drupal-content-by-moderation-state.md +0 -22
  153. package/.claude/commands/drupal-create-block.md +0 -21
  154. package/.claude/commands/drupal-create-taxonomy-term.md +0 -23
  155. package/.claude/commands/drupal-create-user.md +0 -24
  156. package/.claude/commands/drupal-delete-media.md +0 -22
  157. package/.claude/commands/drupal-delete-node.md +0 -23
  158. package/.claude/commands/drupal-delete-taxonomy-term.md +0 -22
  159. package/.claude/commands/drupal-drush-cache-rebuild.md +0 -16
  160. package/.claude/commands/drupal-drush-config-export.md +0 -16
  161. package/.claude/commands/drupal-drush-config-import.md +0 -16
  162. package/.claude/commands/drupal-drush-config-status.md +0 -16
  163. package/.claude/commands/drupal-drush-cron.md +0 -16
  164. package/.claude/commands/drupal-drush-module-disable.md +0 -21
  165. package/.claude/commands/drupal-drush-module-enable.md +0 -19
  166. package/.claude/commands/drupal-drush-module-list.md +0 -17
  167. package/.claude/commands/drupal-drush-security-updates.md +0 -16
  168. package/.claude/commands/drupal-drush-status.md +0 -16
  169. package/.claude/commands/drupal-drush-updatedb.md +0 -16
  170. package/.claude/commands/drupal-drush-user-create.md +0 -22
  171. package/.claude/commands/drupal-drush-user-list.md +0 -19
  172. package/.claude/commands/drupal-drush-watchdog.md +0 -19
  173. package/.claude/commands/drupal-entity-delete.md +0 -24
  174. package/.claude/commands/drupal-entity-get.md +0 -22
  175. package/.claude/commands/drupal-find-orphaned-media.md +0 -18
  176. package/.claude/commands/drupal-get-entity-schema.md +0 -20
  177. package/.claude/commands/drupal-get-media.md +0 -20
  178. package/.claude/commands/drupal-get-node.md +0 -20
  179. package/.claude/commands/drupal-get-taxonomy-term.md +0 -20
  180. package/.claude/commands/drupal-get-taxonomy-terms.md +0 -21
  181. package/.claude/commands/drupal-get-user-by-name.md +0 -19
  182. package/.claude/commands/drupal-get-user.md +0 -19
  183. package/.claude/commands/drupal-graphql-introspect.md +0 -17
  184. package/.claude/commands/drupal-list-content-types.md +0 -16
  185. package/.claude/commands/drupal-list-entity-types.md +0 -16
  186. package/.claude/commands/drupal-list-media-types.md +0 -16
  187. package/.claude/commands/drupal-list-media.md +0 -21
  188. package/.claude/commands/drupal-list-moderation-states.md +0 -20
  189. package/.claude/commands/drupal-list-roles.md +0 -16
  190. package/.claude/commands/drupal-list-users.md +0 -20
  191. package/.claude/commands/drupal-list-vocabularies.md +0 -16
  192. package/.claude/commands/drupal-mcp-whoami.md +0 -16
  193. package/.claude/commands/drupal-report-404-log.md +0 -17
  194. package/.claude/commands/drupal-report-accessibility-audit.md +0 -18
  195. package/.claude/commands/drupal-report-alias-coverage.md +0 -18
  196. package/.claude/commands/drupal-report-broken-embeds.md +0 -18
  197. package/.claude/commands/drupal-report-cache-config.md +0 -16
  198. package/.claude/commands/drupal-report-config-drift.md +0 -16
  199. package/.claude/commands/drupal-report-content-by-author.md +0 -18
  200. package/.claude/commands/drupal-report-content-summary.md +0 -16
  201. package/.claude/commands/drupal-report-duplicate-content.md +0 -18
  202. package/.claude/commands/drupal-report-field-completeness.md +0 -21
  203. package/.claude/commands/drupal-report-menu-integrity.md +0 -17
  204. package/.claude/commands/drupal-report-module-audit.md +0 -16
  205. package/.claude/commands/drupal-report-orphan-pages.md +0 -18
  206. package/.claude/commands/drupal-report-permission-audit.md +0 -16
  207. package/.claude/commands/drupal-report-pii-exposure.md +0 -19
  208. package/.claude/commands/drupal-report-readability.md +0 -19
  209. package/.claude/commands/drupal-report-recently-published.md +0 -18
  210. package/.claude/commands/drupal-report-redirect-health.md +0 -17
  211. package/.claude/commands/drupal-report-revision-hotspots.md +0 -18
  212. package/.claude/commands/drupal-report-scheduled-content.md +0 -18
  213. package/.claude/commands/drupal-report-seo-meta-coverage.md +0 -19
  214. package/.claude/commands/drupal-report-stale-content.md +0 -20
  215. package/.claude/commands/drupal-report-status-report.md +0 -17
  216. package/.claude/commands/drupal-report-taxonomy-usage.md +0 -22
  217. package/.claude/commands/drupal-report-text-format-audit.md +0 -16
  218. package/.claude/commands/drupal-report-translation-coverage.md +0 -19
  219. package/.claude/commands/drupal-report-unpublished.md +0 -18
  220. package/.claude/commands/drupal-report-user-activity.md +0 -18
  221. package/.claude/commands/drupal-report-workflow-bottlenecks.md +0 -20
  222. package/.claude/commands/drupal-search-content.md +0 -22
  223. package/.claude/commands/drupal-search.md +0 -21
  224. package/.claude/commands/drupal-security-info.md +0 -16
  225. package/.claude/commands/drupal-set-moderation-state.md +0 -21
  226. package/.claude/commands/drupal-site-info.md +0 -16
  227. package/.claude/commands/drupal-update-media.md +0 -23
  228. package/.claude/commands/drupal-update-taxonomy-term.md +0 -23
  229. package/.claude/commands/drupal-update-user.md +0 -25
@@ -0,0 +1,157 @@
1
+ #!/usr/bin/env node
2
+ /**
3
+ * install-commands.js — copy generated `/drupal-*` stubs into an operator's
4
+ * client home directories.
5
+ *
6
+ * The canonical files live in `.agents/commands/` (in-repo, harness-agnostic).
7
+ * Claude Code and Grok only auto-load project commands from vendor paths
8
+ * (`.claude/commands/`, `.grok/commands/`). This script writes the *user-level*
9
+ * copies so `/drupal-*` works in every project without committing a vendor
10
+ * folder to this repo or to a consuming application.
11
+ *
12
+ * Default targets: `~/.claude/commands` (Claude adapter) and `~/.grok/commands`
13
+ * (canonical files). Pass `--clients` to subset. Never writes into a project
14
+ * tree.
15
+ *
16
+ * Run: `npm run install:commands -- [--home DIR] [--clients claude,grok,agents]`
17
+ */
18
+
19
+ import { mkdirSync, readdirSync, rmSync, writeFileSync, realpathSync } from "fs";
20
+ import { homedir } from "os";
21
+ import { join, resolve } from "path";
22
+ import { pathToFileURL } from "url";
23
+
24
+ import { allDefinitions } from "../src/tools/index.js";
25
+ import {
26
+ commandFileName,
27
+ renderCommandMarkdown,
28
+ renderClaudeCommandMarkdown,
29
+ } from "./generate-commands.js";
30
+
31
+ /** Whitelisted install targets. `rel` is under `--home` (default: os.homedir()). */
32
+ export const CLIENTS = {
33
+ claude: {
34
+ rel: ".claude/commands",
35
+ render: renderClaudeCommandMarkdown,
36
+ },
37
+ grok: {
38
+ rel: ".grok/commands",
39
+ render: renderCommandMarkdown,
40
+ },
41
+ agents: {
42
+ rel: ".agents/commands",
43
+ render: renderCommandMarkdown,
44
+ },
45
+ };
46
+
47
+ const DEFAULT_CLIENTS = ["claude", "grok"];
48
+
49
+ /**
50
+ * Parse CLI flags. Unknown flags throw.
51
+ *
52
+ * @param {string[]} argv - Arguments after the script name.
53
+ * @returns {{home?: string, clients: string[], help?: boolean}}
54
+ */
55
+ export function parseArgs(argv) {
56
+ const out = { clients: [...DEFAULT_CLIENTS] };
57
+ for (let i = 0; i < argv.length; i++) {
58
+ const a = argv[i];
59
+ if (a === "--help" || a === "-h") {
60
+ out.help = true;
61
+ continue;
62
+ }
63
+ if (a === "--home") {
64
+ out.home = argv[++i];
65
+ if (!out.home) throw new Error("--home requires a directory");
66
+ continue;
67
+ }
68
+ if (a.startsWith("--home=")) {
69
+ out.home = a.slice("--home=".length);
70
+ continue;
71
+ }
72
+ if (a === "--clients") {
73
+ const raw = argv[++i];
74
+ if (!raw) throw new Error("--clients requires a comma-separated list");
75
+ out.clients = splitClients(raw);
76
+ continue;
77
+ }
78
+ if (a.startsWith("--clients=")) {
79
+ out.clients = splitClients(a.slice("--clients=".length));
80
+ continue;
81
+ }
82
+ throw new Error(`Unknown argument: ${a}`);
83
+ }
84
+ return out;
85
+ }
86
+
87
+ function splitClients(raw) {
88
+ const names = String(raw).split(",").map((s) => s.trim()).filter(Boolean);
89
+ if (!names.length) throw new Error("--clients requires at least one client");
90
+ return names;
91
+ }
92
+
93
+ /**
94
+ * Write one `drupal-*.md` per tool into each requested client directory,
95
+ * pruning stale stubs first. Unknown client names fail closed.
96
+ *
97
+ * @param {object} [options]
98
+ * @param {string} [options.home] - Install root (default: os.homedir()).
99
+ * @param {string[]} [options.clients] - Subset of CLIENTS keys.
100
+ * @param {Array<object>} [options.definitions]
101
+ * @returns {Array<{client: string, dir: string, written: string[]}>}
102
+ */
103
+ export function install(options = {}) {
104
+ const home = resolve(options.home || homedir());
105
+ const names = options.clients || DEFAULT_CLIENTS;
106
+ const definitions = options.definitions || allDefinitions;
107
+
108
+ const results = [];
109
+ for (const name of names) {
110
+ const client = CLIENTS[name];
111
+ if (!client) {
112
+ throw new Error(`Unknown client "${name}". Allowed: ${Object.keys(CLIENTS).join(", ")}`);
113
+ }
114
+ const dir = join(home, client.rel);
115
+ mkdirSync(dir, { recursive: true });
116
+ for (const f of readdirSync(dir)) {
117
+ if (/^drupal-.*\.md$/.test(f)) rmSync(join(dir, f));
118
+ }
119
+ const written = [];
120
+ for (const def of definitions) {
121
+ const file = commandFileName(def);
122
+ writeFileSync(join(dir, file), client.render(def));
123
+ written.push(file);
124
+ }
125
+ results.push({ client: name, dir, written });
126
+ }
127
+ return results;
128
+ }
129
+
130
+ const HELP = `Usage: node scripts/install-commands.js [--home DIR] [--clients claude,grok,agents]
131
+
132
+ Copy generated /drupal-* command stubs into operator home directories.
133
+ Does not write into a project tree.
134
+
135
+ --home DIR Install root (default: the current user's home)
136
+ --clients LIST Comma-separated subset of: claude, grok, agents
137
+ (default: claude,grok)
138
+ `;
139
+
140
+ const invokedDirectly =
141
+ process.argv[1] && pathToFileURL(realpathSync(process.argv[1])).href === import.meta.url;
142
+ if (invokedDirectly) {
143
+ try {
144
+ const opts = parseArgs(process.argv.slice(2));
145
+ if (opts.help) {
146
+ console.error(HELP);
147
+ process.exit(0);
148
+ }
149
+ const results = install(opts);
150
+ for (const r of results) {
151
+ console.error(`[install-commands] wrote ${r.written.length} files to ${r.dir} (${r.client})`);
152
+ }
153
+ } catch (err) {
154
+ console.error(`[install-commands] ${err.message}`);
155
+ process.exit(1);
156
+ }
157
+ }
@@ -93,7 +93,10 @@ export class Backend {
93
93
 
94
94
  /**
95
95
  * Update an entity.
96
- * @param {{entityType: string, bundle: string, id: string, attributes?: object, relationships?: object}} _input
96
+ * @param {{entityType: string, bundle: string, id: string, attributes?: object, relationships?: object, resourceVersion?: string}} _input
97
+ * `resourceVersion` is a JSON:API revision selector (`rel:working-copy`,
98
+ * `rel:latest-version`, `id:<vid>`). Backends that cannot address revisions
99
+ * ignore it.
97
100
  * @returns {Promise<import("../canonical.js").CanonicalEntity>}
98
101
  */
99
102
  async updateEntity(_input) { return notImplemented("updateEntity"); }
@@ -139,6 +142,22 @@ export class Backend {
139
142
  */
140
143
  async getEntitySchema(_entityType, _bundle) { return notImplemented("getEntitySchema"); }
141
144
 
145
+ /**
146
+ * Resolve Field API metadata for one field (internal introspection).
147
+ *
148
+ * `getEntitySchema` is sampling-only and does NOT expose `allowed_formats`.
149
+ * The JSON:API adapter reads `field_config` via an internal fetch — not via
150
+ * agent entity tools (`field_config` is on the agent deny list). Returns
151
+ * null when the definition cannot be resolved. Callers must not invent
152
+ * `allowed_formats` from `defaultTextFormat` or `full_html`.
153
+ *
154
+ * Default: unknown. GraphQL is read-only for writes; write sites use JSON:API.
155
+ *
156
+ * @param {{entityType: string, bundle: string, fieldName: string}} _ref
157
+ * @returns {Promise<?{fieldName: string, fieldType: ?string, allowedFormats: string[]}>}
158
+ */
159
+ async getFieldDefinition(_ref) { return null; }
160
+
142
161
  /**
143
162
  * List user roles.
144
163
  * @returns {Promise<Array<{id: string, machineName: string, label: string, weight: number}>>}
@@ -9,6 +9,7 @@
9
9
 
10
10
  import { drupalFetch, drupalUploadFile } from "../drupal-fetch.js";
11
11
  import { validateUuid, validateMachineName } from "../validate.js";
12
+ import { parseFieldConfigObject } from "../field-definition.js";
12
13
  import { Backend } from "./backend-interface.js";
13
14
  import {
14
15
  makeCanonicalEntity,
@@ -208,8 +209,10 @@ export class JsonApiBackend extends Backend {
208
209
  if (BASE_ATTRIBUTE_FIELDS.includes(k)) return false;
209
210
  if (INTERNAL_ATTR_RE.test(k)) {
210
211
  // Paragraph ERR attach needs the current revision id (#192).
212
+ // Node / revisionable writes need the working vs live vid (#166).
211
213
  // Other drupal_internal__* attributes stay stripped.
212
- return entityType === "paragraph" && k === "drupal_internal__revision_id";
214
+ return k === "drupal_internal__vid"
215
+ || (entityType === "paragraph" && k === "drupal_internal__revision_id");
213
216
  }
214
217
  return true;
215
218
  })
@@ -339,17 +342,24 @@ export class JsonApiBackend extends Backend {
339
342
  /**
340
343
  * Update an entity via JSON:API PATCH. Retries without `status` on moderated
341
344
  * bundles — see writeWithModerationFallback.
342
- * @param {{entityType: string, bundle: string, id: string, attributes?: object, relationships?: object}} input
345
+ * @param {{entityType: string, bundle: string, id: string, attributes?: object, relationships?: object, resourceVersion?: string}} input
346
+ * `resourceVersion` is a JSON:API revision selector (e.g. `rel:working-copy`).
347
+ * When set, it is appended so a forward revision is PATCHed instead of the
348
+ * canonical default (#166 / Drupal #2795279).
343
349
  * @returns {Promise<import("../canonical.js").CanonicalEntity>} The updated entity.
344
350
  */
345
- async updateEntity({ entityType, bundle, id, attributes = {}, relationships }) {
351
+ async updateEntity({ entityType, bundle, id, attributes = {}, relationships, resourceVersion }) {
346
352
  validateUuid(id);
347
353
  const buildPayload = (attrs) => {
348
354
  const payload = { data: { type: `${entityType}--${bundle}`, id, attributes: attrs } };
349
355
  if (relationships) payload.data.relationships = relationships;
350
356
  return payload;
351
357
  };
352
- const data = await this.writeWithModerationFallback(`${this.resourcePath(entityType, bundle)}/${encodeURIComponent(id)}`, "PATCH", buildPayload, attributes);
358
+ let path = `${this.resourcePath(entityType, bundle)}/${encodeURIComponent(id)}`;
359
+ if (resourceVersion) {
360
+ path += `?resourceVersion=${encodeURIComponent(resourceVersion)}`;
361
+ }
362
+ const data = await this.writeWithModerationFallback(path, "PATCH", buildPayload, attributes);
353
363
  return this.toCanonical(data.data);
354
364
  }
355
365
 
@@ -512,6 +522,39 @@ export class JsonApiBackend extends Backend {
512
522
  });
513
523
  }
514
524
 
525
+ /**
526
+ * Read Field API metadata from JSON:API `field_config` (chain step 1).
527
+ *
528
+ * This is an internal introspection call, not an agent entity-tool read:
529
+ * `field_config` is on the connector deny list. Do not invent
530
+ * `allowed_formats` from `this.site.defaultTextFormat` or `full_html`.
531
+ * Empty / unreadable `field_config` returns null so the caller can try
532
+ * Drush `config:get field.field.{entityType}.{bundle}.{field}` and, if
533
+ * that also fails, keep the historical default chain only while the list
534
+ * is unknown.
535
+ *
536
+ * @param {{entityType: string, bundle: string, fieldName: string}} ref
537
+ * @returns {Promise<?{fieldName: string, fieldType: ?string, allowedFormats: string[]}>}
538
+ */
539
+ async getFieldDefinition({ entityType, bundle, fieldName }) {
540
+ validateMachineName(entityType, "entityType");
541
+ validateMachineName(bundle, "bundle");
542
+ validateMachineName(fieldName, "fieldName");
543
+ const params = new URLSearchParams();
544
+ params.set("filter[entity_type]", entityType);
545
+ params.set("filter[bundle]", bundle);
546
+ params.set("filter[field_name]", fieldName);
547
+ params.set("page[limit]", "1");
548
+ let data;
549
+ try {
550
+ data = await drupalFetch(this.site, `/jsonapi/field_config/field_config?${params}`);
551
+ } catch {
552
+ return null;
553
+ }
554
+ const row = Array.isArray(data?.data) ? data.data[0] : data?.data;
555
+ return parseFieldConfigObject(row?.attributes, fieldName);
556
+ }
557
+
515
558
  /**
516
559
  * Describe a bundle's fields by sampling one entity and inferring attribute
517
560
  * types from its values. JSON:API has no schema endpoint, so an empty bundle
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Decide whether the `summary` argument may be written onto `body`.
3
+ *
4
+ * Core `text_with_summary` stores a `summary` property. `text_long` and
5
+ * sampled `text_formatted` do not — Drupal drops an unknown property, so a
6
+ * silent write would lose the teaser. Fail closed when the sampled schema
7
+ * cannot confirm the property exists (#163).
8
+ */
9
+
10
+ /** Warning attached when `summary` is written onto a text_with_summary body. */
11
+ export const SUMMARY_DEPRECATED_WARNING = {
12
+ code: "summary_parameter_deprecated",
13
+ message:
14
+ "The `summary` argument writes body.summary on core text_with_summary. " +
15
+ "Prefer setting the site's dedicated deck/summary field via `fields`.",
16
+ };
17
+
18
+ /**
19
+ * @param {object} backend Backend with `getEntitySchema`.
20
+ * @param {string} bundle Node bundle machine name.
21
+ * @param {string|undefined} summary Caller-supplied summary; undefined = omitted.
22
+ * @returns {Promise<{deprecated: boolean}>} Whether a deprecation notice applies.
23
+ * @throws {Error} When `summary` was supplied and the body field has no
24
+ * summary property, or the schema cannot be determined.
25
+ */
26
+ export async function assertBodySummaryWritable(backend, bundle, summary) {
27
+ if (summary === undefined) return { deprecated: false };
28
+
29
+ let schema;
30
+ try {
31
+ schema = await backend.getEntitySchema("node", bundle);
32
+ } catch (err) {
33
+ const detail = err instanceof Error ? err.message : String(err);
34
+ throw new Error(
35
+ `Cannot write \`summary\`: the body field schema for node.${bundle} could not be determined (${detail}). Set the site's dedicated deck field via \`fields\` instead.`,
36
+ );
37
+ }
38
+
39
+ const bodyType = schema?.attributes?.body;
40
+ if (bodyType === "text_with_summary") {
41
+ return { deprecated: true };
42
+ }
43
+
44
+ const sampled = bodyType ? ` (sampled as ${bodyType})` : "";
45
+ throw new Error(
46
+ `This site's body field has no summary property${sampled}; set the site's deck field via \`fields\`. The \`summary\` argument is only valid for core text_with_summary body fields.`,
47
+ );
48
+ }
49
+
50
+ /**
51
+ * Attach the `summary` deprecation notice to a write (or dryRun) result.
52
+ *
53
+ * @param {object|null|undefined} result Tool response.
54
+ * @returns {object|null|undefined} Result with `_warnings` when result is an object.
55
+ */
56
+ export function attachSummaryDeprecation(result) {
57
+ if (!result || typeof result !== "object") return result;
58
+ const existing = Array.isArray(result._warnings) ? result._warnings : [];
59
+ return { ...result, _warnings: [...existing, SUMMARY_DEPRECATED_WARNING] };
60
+ }