wpops 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.
- package/README.md +111 -7
- package/bin/wpops.js +201 -244
- package/lib/abilities.js +51 -0
- package/lib/app-passwords.js +39 -0
- package/lib/args.js +10 -35
- package/lib/batch.js +54 -0
- package/lib/blocks.js +72 -0
- package/lib/commands.js +323 -83
- package/lib/env.js +2 -1
- package/lib/errors.js +10 -0
- package/lib/global-styles.js +25 -0
- package/lib/health.js +41 -0
- package/lib/help.js +117 -0
- package/lib/introspect.js +59 -0
- package/lib/navigation.js +71 -0
- package/lib/revisions.js +56 -0
- package/lib/run.js +509 -0
- package/lib/search.js +31 -0
- package/lib/settings.js +74 -0
- package/lib/spec.js +391 -0
- package/lib/templates.js +69 -0
- package/lib/util.js +143 -0
- package/lib/widgets.js +93 -0
- package/package.json +3 -2
- package/skills/wpops/SKILL.md +56 -34
package/lib/widgets.js
ADDED
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
// 小工具与边栏(/wp-json/wp/v2/widgets、/wp-json/wp/v2/sidebars)。
|
|
2
|
+
import { printJson, dry, listRequest, parseJson, readJsonFile } from './util.js';
|
|
3
|
+
import { UsageError } from './errors.js';
|
|
4
|
+
|
|
5
|
+
const WIDGETS = '/wp-json/wp/v2/widgets';
|
|
6
|
+
const SIDEBARS = '/wp-json/wp/v2/sidebars';
|
|
7
|
+
|
|
8
|
+
/** 从 --instance / --from-file 解析小工具实例 JSON。 */
|
|
9
|
+
function instanceBody(flags) {
|
|
10
|
+
if (flags['from-file'] !== undefined) return readJsonFile(flags['from-file']);
|
|
11
|
+
if (flags.instance !== undefined) return parseJson(flags.instance);
|
|
12
|
+
return undefined;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
export async function listWidgets(client, flags) {
|
|
16
|
+
const query = {
|
|
17
|
+
per_page: flags['per-page'] ?? 100,
|
|
18
|
+
page: flags.page,
|
|
19
|
+
sidebar: flags.sidebar,
|
|
20
|
+
};
|
|
21
|
+
const { data, total } = await listRequest(client, WIDGETS, query, flags);
|
|
22
|
+
if (flags.json) return printJson(data);
|
|
23
|
+
console.log(`小工具 ${total ?? data.length} 个:`);
|
|
24
|
+
for (const w of data) {
|
|
25
|
+
console.log(` ${w.id} [${w.sidebar}] ${w.type ?? ''}`);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
export async function getWidget(client, id, flags) {
|
|
30
|
+
const { data } = await client.request('GET', `${WIDGETS}/${encodeURIComponent(id)}`);
|
|
31
|
+
if (flags.json) return printJson(data);
|
|
32
|
+
console.log(`${data.id} [${data.sidebar}] ${data.type ?? ''}`);
|
|
33
|
+
console.log(JSON.stringify(data.instance ?? {}, null, 2));
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
export async function createWidget(client, flags) {
|
|
37
|
+
if (flags.id === undefined || flags.sidebar === undefined) {
|
|
38
|
+
throw new UsageError('创建小工具需要 --id 与 --sidebar');
|
|
39
|
+
}
|
|
40
|
+
const body = { id: String(flags.id), sidebar: String(flags.sidebar) };
|
|
41
|
+
const instance = instanceBody(flags);
|
|
42
|
+
body.instance = instance === undefined ? {} : instance;
|
|
43
|
+
if (flags['dry-run']) return dry(`创建小工具 ${JSON.stringify(body)}`);
|
|
44
|
+
const { data } = await client.request('POST', WIDGETS, { body });
|
|
45
|
+
if (flags.json) return printJson(data);
|
|
46
|
+
console.log(`✓ 已创建小工具 ${data.id} [${data.sidebar}]`);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
export async function updateWidget(client, id, flags) {
|
|
50
|
+
const body = {};
|
|
51
|
+
if (flags.sidebar !== undefined) body.sidebar = String(flags.sidebar);
|
|
52
|
+
const instance = instanceBody(flags);
|
|
53
|
+
if (instance !== undefined) body.instance = instance;
|
|
54
|
+
if (!Object.keys(body).length) throw new UsageError('更新小工具需要 --sidebar 或 --instance/--from-file');
|
|
55
|
+
if (flags['dry-run']) return dry(`更新小工具 ${id} ${JSON.stringify(body)}`);
|
|
56
|
+
const { data } = await client.request('POST', `${WIDGETS}/${encodeURIComponent(id)}`, { body });
|
|
57
|
+
if (flags.json) return printJson(data);
|
|
58
|
+
console.log(`✓ 已更新小工具 ${data.id} [${data.sidebar}]`);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
export async function deleteWidget(client, id, flags) {
|
|
62
|
+
if (flags['dry-run']) return dry(`删除小工具 ${id}`);
|
|
63
|
+
if (!flags.force) throw new UsageError('删除小工具需要 --force(不可撤销)');
|
|
64
|
+
const { data } = await client.request('DELETE', `${WIDGETS}/${encodeURIComponent(id)}`);
|
|
65
|
+
if (flags.json) return printJson(data);
|
|
66
|
+
console.log(data.deleted ? `✓ 已删除小工具 ${id}` : `? 小工具 ${id} 未删除`);
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
export async function listSidebars(client, flags) {
|
|
70
|
+
const { data } = await client.request('GET', SIDEBARS);
|
|
71
|
+
if (flags.json) return printJson(data);
|
|
72
|
+
console.log(`边栏 ${data.length} 个:`);
|
|
73
|
+
for (const s of data) {
|
|
74
|
+
console.log(` ${s.id} ${s.name} widgets=[${(s.widgets || []).join(',')}]`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
export async function getSidebar(client, id, flags) {
|
|
79
|
+
const { data } = await client.request('GET', `${SIDEBARS}/${encodeURIComponent(id)}`);
|
|
80
|
+
if (flags.json) return printJson(data);
|
|
81
|
+
console.log(`${data.id} ${data.name}`);
|
|
82
|
+
console.log(` widgets=[${(data.widgets || []).join(',')}]`);
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
export async function updateSidebar(client, id, flags) {
|
|
86
|
+
if (flags.widgets === undefined) throw new UsageError('更新边栏需要 --widgets a,b,c');
|
|
87
|
+
const widgets = String(flags.widgets).split(',').map((s) => s.trim()).filter(Boolean);
|
|
88
|
+
const body = { widgets };
|
|
89
|
+
if (flags['dry-run']) return dry(`更新边栏 ${id} ${JSON.stringify(body)}`);
|
|
90
|
+
const { data } = await client.request('POST', `${SIDEBARS}/${encodeURIComponent(id)}`, { body });
|
|
91
|
+
if (flags.json) return printJson(data);
|
|
92
|
+
console.log(`✓ 已更新边栏 ${data.id} widgets=[${(data.widgets || []).join(',')}]`);
|
|
93
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "wpops",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "WordPress REST operations toolkit — manage posts, pages, media, comments, users and plugins from the CLI. Multi-site, safety-first, zero dependencies.",
|
|
3
|
+
"version": "0.2.0",
|
|
4
|
+
"description": "WordPress REST operations toolkit — manage posts, pages, media, menus, comments, users, custom types, revisions, blocks, templates, widgets, settings and plugins from the CLI. Multi-site, safety-first, zero dependencies.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "qnnp",
|
|
7
7
|
"type": "module",
|
|
@@ -24,6 +24,7 @@
|
|
|
24
24
|
"cli",
|
|
25
25
|
"application-password",
|
|
26
26
|
"content-management",
|
|
27
|
+
"navigation-menu",
|
|
27
28
|
"multi-site"
|
|
28
29
|
],
|
|
29
30
|
"repository": {
|
package/skills/wpops/SKILL.md
CHANGED
|
@@ -1,54 +1,76 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: wpops
|
|
3
|
-
description: Use when managing a WordPress site through wpops, the REST CLI (npm package `wpops`, command `wpops`) —
|
|
3
|
+
description: Use when managing a WordPress site through wpops, the zero-dependency REST CLI (npm package `wpops`, command `wpops`) — posts, pages, media, menus, comments, users, custom post types, search, settings, revisions, blocks, block-theme navigation/templates/global styles, widgets, application passwords, plugins, themes, Abilities, site health, or batch requests; configuring a site; or operating across multiple configured sites. Covers the required safe workflow (doctor first, --dry-run before writes, --all needs --yes) and the REST capability boundary. Prefer wpops over hand-written curl or raw HTTP for any WordPress task.
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# wpops —— WordPress REST 操作
|
|
7
7
|
|
|
8
8
|
## 何时用
|
|
9
|
-
用户要管理 WordPress
|
|
9
|
+
用户要管理 WordPress 站点的**内容 / 结构 / 配置 / 插件主题**时,优先用 `wpops`,不要手搓 curl。
|
|
10
|
+
本 skill 自洽,不依赖其他 skill;需要精确细节时**现查现用**(见下)。
|
|
10
11
|
|
|
11
12
|
## 安装与位置
|
|
12
|
-
- 全局命令 `wpops`(`npm i -g wpops`
|
|
13
|
-
-
|
|
14
|
-
-
|
|
13
|
+
- 全局命令 `wpops`(`npm i -g wpops` 安装;postinstall 会顺带装好本 skill)。
|
|
14
|
+
- 未安装时也可:`npx wpops ...`。
|
|
15
|
+
- 配置目录:默认包目录下的 `sites/`;设了 `WPOPS_HOME` 则读 `$WPOPS_HOME/sites/`。
|
|
15
16
|
- 凭据:`sites/<名字>.env`。**不要读取、不要打印其中的应用密码。**
|
|
16
17
|
- 首次配置:`wpops setup`(交互式写入凭据并自动体检)。
|
|
17
18
|
|
|
18
19
|
## 站点与多站
|
|
19
|
-
- `wpops sites` —
|
|
20
|
-
- `wpops -s <名字> ...` —
|
|
21
|
-
- `wpops --all ...` —
|
|
20
|
+
- `wpops sites` — 列出已配置站点;`sites show <名>`(密码打码)、`sites rm`、`sites rename`。
|
|
21
|
+
- `wpops -s <名字> ...` — 指定站点;只有一个站点时可省略;`-s a,b` 可指定多个。
|
|
22
|
+
- `wpops --all ...` — 对所有站点批量执行。
|
|
22
23
|
|
|
23
24
|
## 安全流程(必须遵守)
|
|
24
25
|
1. 首次操作某站先跑 `wpops doctor`(检查连接与权限;失败返回非 0)。
|
|
25
|
-
2. 任何写操作先加 `--dry-run`
|
|
26
|
+
2. 任何写操作先加 `--dry-run` 预览(`delete` 的预览不需要 `--force`;真正删除才加 `--force`)。
|
|
26
27
|
3. `--all` 的**写操作必须加 `--yes`**,否则会被拒绝。
|
|
27
|
-
4. 需要程序化解析输出时加 `--json`。
|
|
28
|
+
4. 需要程序化解析输出时加 `--json`;要投影/表格用 `--fields a,b` / `--csv` / `--table`。
|
|
28
29
|
5. 应用密码绝不写进命令、日志或对话。
|
|
29
|
-
6. 破坏性操作(`delete --force
|
|
30
|
-
|
|
31
|
-
## 常用命令
|
|
32
|
-
```
|
|
33
|
-
wpops doctor
|
|
34
|
-
wpops setup
|
|
35
|
-
wpops posts list|get|create|update|delete [--per-page N] [--page N] [--status s] [--search 词] [--orderby date] [--order desc] [--categories 1,2]
|
|
36
|
-
wpops pages ... # 同 posts
|
|
37
|
-
wpops media list|get|upload|update|delete [--alt "文本"]
|
|
38
|
-
wpops categories list|create|update|delete ...
|
|
39
|
-
wpops tags list|create|update|delete ...
|
|
40
|
-
wpops comments list|get|update|delete [--status hold|approved|spam|trash]
|
|
41
|
-
wpops users list|get|create|update|delete|me ...
|
|
42
|
-
wpops plugins list|install <别名>|activate <plugin>|deactivate <plugin>
|
|
43
|
-
wpops themes list|activate <stylesheet>
|
|
44
|
-
wpops install-skill # 手动补装本 skill
|
|
45
|
-
wpops raw <METHOD> <path> [--data JSON] # 兜底:任意端点
|
|
46
|
-
```
|
|
30
|
+
6. 破坏性操作(`delete --force`、分类/用户/菜单项/插件删除)先确认目标存在且正确。
|
|
31
|
+
7. 退出码:`0` 成功;`2` 用法/参数错误(stderr 会给用法与 `--help` 提示);`1` 运行期错误(HTTP/网络/配置)。脚本据此判断成败,不要只看有没有输出。
|
|
47
32
|
|
|
48
|
-
##
|
|
49
|
-
-
|
|
50
|
-
|
|
51
|
-
-
|
|
33
|
+
## 现查现用(不要背细节)
|
|
34
|
+
- `wpops <组> --help`、`wpops help <组> [动作]` — 该组动作与参数(**首选**)。
|
|
35
|
+
- `wpops commands --json` — 机器可读清单:全部组、动作、是否写操作、别名。
|
|
36
|
+
- `wpops raw GET /wp-json/` — 站点真实命名空间与路由。
|
|
37
|
+
- `wpops raw OPTIONS <path>` — 某端点支持的字段、类型、枚举、必填。
|
|
38
|
+
- 字段语义以 WordPress 官方 REST 文档为准。
|
|
39
|
+
|
|
40
|
+
## 能力索引(组 → 一句话)
|
|
41
|
+
内置:`doctor`(连接体检) · `me`(当前用户) · `setup`(配置站点) · `sites`(站点档案) · `install-skill` · `completion` · `commands`(命令清单)。
|
|
42
|
+
|
|
43
|
+
内容与结构:
|
|
44
|
+
- `posts` / `pages` — 文章/页面增删改查。
|
|
45
|
+
- `content <rest_base>` / `terms <rest_base>` — 任意 `show_in_rest` 的 CPT / 自定义分类。
|
|
46
|
+
- `media` — 媒体列出/上传/URL 导入(`sideload`)/图像编辑(`edit-image` 裁剪旋转)/更新/删除。
|
|
47
|
+
- `categories` / `tags` — 分类/标签。
|
|
48
|
+
- `comments` — 评论(含创建/回复)。
|
|
49
|
+
- `users` — 用户(含 `me`)。
|
|
50
|
+
- `menus` / `menu-items` — 导航菜单与菜单项(`--locations` 绑定主题位置)。
|
|
51
|
+
- `revisions` — 修订列表/查看/删除/恢复。
|
|
52
|
+
- `search` — 跨内容搜索。
|
|
53
|
+
- `settings` — 站点设置读取/更新。
|
|
54
|
+
- `types` / `taxonomies` / `statuses` — 自省已注册类型/分类/状态。
|
|
52
55
|
|
|
53
|
-
|
|
54
|
-
|
|
56
|
+
区块主题与现代内容:
|
|
57
|
+
- `blocks` — 可复用区块。
|
|
58
|
+
- `navigation` — 区块主题 Navigation。
|
|
59
|
+
- `templates` / `template-parts` — 模板与片段。
|
|
60
|
+
- `global-styles` — theme.json 全局样式。
|
|
61
|
+
- `widgets` / `sidebars` — 小工具/边栏。
|
|
62
|
+
- `app-passwords` — 用户应用密码(创建时明文只打印一次)。
|
|
63
|
+
|
|
64
|
+
运维与插件:
|
|
65
|
+
- `plugins` — 列表/安装/启停/删除。
|
|
66
|
+
- `themes` — 列表/切换。
|
|
67
|
+
- `abilities` — Abilities API 列出/运行(需插件)。
|
|
68
|
+
- `health` — 站点健康测试(需插件)。
|
|
69
|
+
- `batch` — 批量 REST 请求(仅 POST/PUT/PATCH/DELETE)。
|
|
70
|
+
- `raw` — 任意端点兜底。
|
|
71
|
+
|
|
72
|
+
## 能力边界(不要向用户承诺)
|
|
73
|
+
- **REST 做不到**:插件/主题/核心**代码更新**、备份/还原、安全扫描、性能优化、文件系统操作 → 需 WP-CLI + SSH 或集中管理平台。
|
|
74
|
+
- **能做的**:上面索引里的全部内容,以及连接与权限体检。
|
|
75
|
+
- **需要插件**:导航菜单(`/menus`、`/menu-items`)、`abilities`、`health`;路由不存在时端点会报 404,wpops 会提示"需插件",此时回退到 `wpops raw` 或后台操作。
|
|
76
|
+
- **`batch` 只接受 POST/PUT/PATCH/DELETE**(核心 `/batch/v1` 拒绝 GET)。
|